home *** CD-ROM | disk | FTP | other *** search
/ 9-Digit Zip Code Directory / 9-Digit Zip Code Directory (American Business Information) (ABIZIP-12).ISO / z4src.zip / BSCFG.C < prev    next >
C/C++ Source or Header  |  1995-09-20  |  76KB  |  2,500 lines

  1. //----------------------------------------------------------------------------
  2. //                            MODULE DESCRIPTION
  3. //
  4. //  Module:    bscfg.c
  5. //   Title:    Base library
  6. //  Notice:    John M. Weeder
  7. //                 Copyright (c) 1993. All rights reserved.
  8. //             This module contains proprietary information and should be 
  9. //                treated as confidential.
  10. //
  11. //----------------------------------------------------------------------------
  12. //                           MAINTENANCE HISTORY
  13. //
  14. // $Workfile$
  15. // $Revision$
  16. //   $Author$
  17. //     $Date$
  18. //      $Log$    
  19. //
  20. //----------------------------------------------------------------------------
  21. //                             MODULE NARRATIVE
  22. //
  23. //
  24. //    This module contains code to manage a program configuration file.
  25. //    Each program has a configuration file opened automatically at startup.
  26. //    If it does not exist, the file is created in the same directory as the
  27. //    executable or in the current directory. Configuration files have an 
  28. //    extension of '.cfg'
  29. //
  30. //    A configuration file consists of an arbitrary number of named data items.
  31. //    Each data item is called a KEY.
  32. //
  33. //    A key entry currently has a maximum length of 256. There are 17 bytes of 
  34. //    overhead in the key (including null terminator on name). Allowing room
  35. //    for a minimum of 4 bytes of data in the key, there is a maximum key name 
  36. //    length of 256 - 25 - 4 = 227 bytes.
  37. //
  38. //    Each configuration file has a file header. This header contains some text
  39. //    information which may be viewed by 'type'ing of 'cat'ing the file. The
  40. //    header also contains some version information, the location of the root node
  41. //    of the data tree, and the start of the linked list of empty blocks.
  42. //
  43. //    The remainder of the file is divided into a series of arbitrary length
  44. //    BLOCKs. Each block contain a short header which has the size of the block,
  45. //    the amount of data in the block, and links for the empty block list. Blocks
  46. //    follow, one after the other, until the end of the file. All unused blocks
  47. //    are maintained in a linked list.
  48. //
  49. //    The data keys are stored in a b-plus tree. Each node in the tree has a
  50. //    fixed size. Each key element has a maximum size. Each nodes contains as
  51. //    many keys as possible, with no key exceeding its maximum size. The maximum
  52. //    depth of the tree is limited by storage for the search pointers. Data for 
  53. //    each key is stored within the key element if it will fit, otherwise it is
  54. //    stored in a separate data block. Keys are inserted with a method that 
  55. //    garuantees a balanced tree, however deleting keys can cause an imbalance.
  56. //    For this reason, deletions should be limited.
  57. //
  58. //    The insertion method is simple. Work down the tree. If the current node 
  59. //    is full, split it. A node is full when it does not have at least KEY_SIZE 
  60. //    bytes free. Note that the max number of keys in a node is not fixed, if 
  61. //    the names and data are small, a large number of keys may fit into a node.
  62. //    The minimum number of keys is fixed at NODE_SIZE / KEY_SIZE.
  63. //
  64. //    This code is not generally re-entrant. Global data has been placed in a
  65. //    structure in case multiple configuration files are desired at some future
  66. //    date. A semaphore is used to resolve mutual access conflicts under OS/2.
  67. //
  68. //    Naming conventions:
  69. //        Names must be <= MAX_CFG_NAME bytes in length.
  70. //        Names should include type information. For example: 'John~STRING' is
  71. //         a string named John. 
  72. //        The type information should follow the name and be separated with a 
  73. //         tilde. Multiple elements of the name should be sepated by a tilde.
  74. //        Example:
  75. //            object1~STRUCT~object2~INT     An integer named object2 in a 
  76. //                                                    structure named object1.
  77. //
  78. //        This naming order is used to that similarly named object (for instance,
  79. //         all objects in a structure) are stored in the same locality.
  80. //
  81. //    NOTES:
  82. //        Disk structures should always use 'SHORT' and 'LONG' and other fixed
  83. //        size data types. NEVER use things such as INT and SIZET. Also, be
  84. //        very careful of structure packing. To be safe, try to align data on
  85. //        4 byte boundaries.
  86. //        Yeah, nodes waste a lot of space in overhead, but they are simpler
  87. //        to maintain than if everything was bit packed into the structure.
  88. //
  89. //    The code in this module should be written entirely in C. 
  90. //    Do not use any C++ constructs.
  91. //
  92. //    This module is portable to:
  93. //        DOS 3.X+
  94. //        MS Windows 3.X+
  95. //        OS/2 2.X+
  96. //        OS/2 2.0 PM
  97. //        SCO UNIX.
  98. //
  99. //    The following compilers are supported:
  100. //        MSC 6.0A
  101. //        MSC/C++ 7.0
  102. //        Borland C++ 3.1 for DOS
  103. //        Borland C++ 1.0 for OS/2 2.X
  104. //        SCO UNIX cc
  105. //
  106. //----------------------------------------------------------------------------
  107. #include <bs.h>
  108.  
  109.  
  110. //----------------------------------------------------------------------------
  111. //    Constants and data types
  112. //----------------------------------------------------------------------------
  113. #define HDR_SIZE        (256)                    //    Size of file header
  114. #define HDR_VER        (0x0100)                // Version
  115. #define HDR_ID            (0x6168DCBAL)        // File id
  116.  
  117. #define BLK_SIZE        (32)                    //    Minimin block size
  118.                                                     // All blocks are some multiple of this
  119. #define NODE_SIZE        (2048)                //    Size of a b-plus tree node
  120.  
  121. #define KEY_SIZE        (256)                    // Key size information
  122.                                                     // Actual size of key structure - padding
  123. #define KEY_OVERHEAD    (sizeof(CFGKEY)-3)
  124.                                                     // Internal maximum name length
  125. #define KEY_NAME        ((KEY_SIZE - KEY_OVERHEAD) - sizeof(LONG))
  126.  
  127. #define MAX_LEVEL        (5)                    // Maximum level of b-plus tree
  128.                                                     // This allows for up to 512K entries!
  129. //
  130. //    The cache for the nodes is allocated dynamically and as
  131. //    needed. 
  132. //
  133. //    Under DOS, the cache shrinks to a minimum level when not in
  134. //    use. Normally, this minimum level is 2. The maximum cache size
  135. //    is the MAX_LEVEL + 1. The extra cache buffer is used when a node is
  136. //    split.
  137. //
  138. #define MAX_CACHE            (MAX_LEVEL + 1)
  139. #if OS_DOS
  140. #    define MIN_CACHE        (2)
  141. #else
  142. #    define MIN_CACHE        (MAX_CACHE)
  143. #endif
  144.  
  145. #if OS_UNIX
  146. #    define LF    "\n"
  147. #else
  148. #    define LF    "\r\n"
  149. #endif
  150.  
  151. #define CTRLZ    "\x1A"
  152.  
  153. typedef struct _CFGHDR                        // Configuration file header
  154. {
  155.     CHAR szText[200];                            // Header description
  156.     LONG lId;                                    // Identifier word
  157.     LONG lRevision;                            // Revision count
  158.     FPOS fposRoot;                                // Position of root node
  159.     FPOS fposEmpty;                            // Address of first empty block or 0
  160.     USHORT usVer;                                // File version
  161.     BYTE bUnused[56 - 4 - 2 - 4 - 4 - 4];
  162. } CFGHDR;                                        // This structure should have
  163. BASETYPE(CFGHDR);                                //  a size of HDR_SIZE bytes
  164.  
  165. typedef struct _CFGBLK                        // Data block header
  166. {
  167.    LONG lSize;                                    // Total size of block 
  168.     LONG lData;                                    // Size of data area
  169.     CRC crcData;                                // CRC of data
  170.     FPOS fposNext;                                // Address of next and previous
  171.     FPOS fposPrev;                                // empty blocks.
  172.     SHORT fEmpty;                                // Block is empty?
  173.     SHORT sUnused;                                // Used to pad to 4 byte boundary
  174. } CFGBLK;                                        // Block header
  175. BASETYPE(CFGBLK);
  176.  
  177. typedef struct _CFGKEY                        // B-Plus tree key
  178. {
  179.     LONG cb;                                        // Total size of this key
  180.     LONG cbData;                                // Size of data
  181.     FPOS fposData;                                // Position of data block or 0
  182.     LONG fLast;                                    // Last key in this node
  183.     FPOS fposLeft;                                // Position of left and right child
  184.     FPOS fposRight;                            //  nodes. Only last key in the node
  185.                                                     //  can have a right child
  186.     CHAR szName[4];                            // Name of key.
  187.                                                     // 1 byte is for null, 3 for padding
  188. } CFGKEY;                                        // This structure is variable length
  189. BASETYPE(CFGKEY);
  190.  
  191. typedef struct _CFGGBL                        // Global data.
  192. {
  193.     BOOL fInit;                                    // Initialized flag
  194.     CFGHDR cfghdr;                                // Current file header
  195.     BOOL fHeaderDirty;                        // Is file header changed?
  196.     SIZET cLock;                                // Lock count
  197.     TID tid;                                        // Thread id of locker
  198.     HF hf;                                        // Config file handle
  199.  
  200.     PBYTE apbCache[MAX_CACHE];                // Cache buffer pointers
  201.    BOOL afCacheUsed[MAX_CACHE];            // Cache buffer used?
  202.    BOOL afCacheDirty[MAX_CACHE];            // Cache buffer dirty?
  203.    FPOS afposCache[MAX_CACHE];            // Disk offset of cache buffer
  204.    LONG alCacheUse[MAX_CACHE];            // Disk offset of cache buffer
  205.     LONG lCacheUse;                            // Usage counter for cache
  206.     SIZET cCacheMin;                            // Minimum cache buffers
  207.  
  208.     PCFGKEY apcfgkeyLevel[MAX_LEVEL];    // Pointer to current key in node
  209.     SIZET acLevelCache[MAX_LEVEL];        //    Cache buffer id for node
  210.  
  211.     SIZET cLevel;                                // Levels read in (first level is root)
  212.  
  213.    LONG lRevision;                            //    Last header revision
  214.                                                     // Used to know when to flush cache
  215.     CHAR szName[MAX_CFG_NAME];                // Last name found!
  216. #if OS_OS2 || OS_PM
  217.     BOOL fSemaphore;                            // Semaphore not created yet!!
  218.     HMTX hmtx;                                    // Access semaphore
  219. #endif
  220.  
  221. } CFGGBL;
  222. BASETYPE(CFGGBL);
  223.  
  224.  
  225. #define CfgKeyNext(x)        ((PCFGKEY)(((PBYTE)(x)) + (SIZET)(x)->cb))
  226. #define CfgNodeBuf(x)         (pg->apbCache[pg->acLevelCache[(x)]])
  227. #define CfgNodeDirty(x)        pg->afCacheDirty[pg->acLevelCache[(x)]] = TRUE
  228. #define CfgNodeKey(x)        (pg->apcfgkeyLevel[(x)])
  229. #define CfgNodeLevel()        (pg->cLevel - 1)
  230. #define CfgNodePos(x)         (pg->afposCache[pg->acLevelCache[(x)]])
  231. #define CfgNodeSetKey(x,y)    (pg->apcfgkeyLevel[(x)] = (y))
  232. #define CfgNodeUsed(x,y)    (pg->afCacheUsed[pg->acLevelCache[(x)]] = (y))
  233.  
  234.  
  235. //----------------------------------------------------------------------------
  236. //    Globals
  237. //----------------------------------------------------------------------------
  238. static CHAR szHeaderFormat[] =            // File header format text
  239.     "Program Configuration File v%d.%02d" LF
  240.     "%s" LF
  241.     "Created %2d:%02d:%02d %2d/%02d/%4d by %s" LF
  242.     CTRLZ;
  243.  
  244. //
  245. //    Global data is always accessed via a pointer. This allows easy conversion
  246. //    to multiple file management in the future.
  247. //
  248. static CFGGBL     g;                                // Global data.
  249. static PCFGGBL pg = &g;                        // Pointer to global data.
  250.  
  251.  
  252. //----------------------------------------------------------------------------
  253. //    Prototypes
  254. //----------------------------------------------------------------------------
  255. #if COMPILER_DEBUG
  256. static VOID FN_L CfgBlockIsAddrValid(FPOS);
  257. static VOID FN_L CfgBlockWalk(BOOL);
  258. #endif
  259.  
  260. static LONG FN_L CfgBlockDataSize(LONG);
  261. static BOOL FN_L CfgBlockMarkEmpty(FPOS);
  262. static BOOL FN_L CfgBlockRead(FPOS, PCFGBLK);
  263. static LONG FN_L CfgBlockTotalSize(LONG);
  264. static BOOL FN_L CfgBlockUseEmpty(FPOS);
  265. static FPOS FN_L CfgBlockWrite(FPOS, PCFGBLK);
  266. static BOOL FN_L CfgCacheFindEmpty(PSIZET);
  267. static BOOL FN_L CfgCacheFlush(SIZET);
  268. static BOOL FN_L CfgCacheFlushAll(void);
  269. static VOID FN_L CfgCacheInvalidate(void);
  270. static BOOL FN_L CfgCacheShrink(void);
  271. static BOOL FN_L CfgCreate(PCSZ);
  272. static BOOL FN_L CfgDataRead(FPOS, PCFGBLK, PBYTE _FAR_ *, PSIZET);
  273. static FPOS FN_L CfgDataWrite(FPOS, PCVOID, LONG);
  274. static BOOL FN_L CfgHeaderRead(void);
  275. static BOOL FN_L CfgHeaderWrite(BOOL);
  276. static SIZET FN_L CfgKeyCount(SIZET, PSIZET);
  277. static BOOL FN_L CfgKeyDelete(void);
  278. static BOOL FN_L CfgKeyFind(PCSZ, PCFGKEY _FAR_ *);
  279. static BOOL FN_L CfgKeyInsert(SIZET, PCFGKEY);
  280. static BOOL FN_L CfgKeyNew(PCSZ, PCFGKEY _FAR_ *, SIZET);
  281. static BOOL FN_L CfgKeyRemove(void);
  282. static BOOL FN_L CfgKeySplit(void);
  283. static BOOL FN_L CfgInitialize(void);
  284. static BOOL FN_L CfgLock(void);
  285. static BOOL FN_L CfgNodeNew(PSIZET);
  286. static BOOL FN_L CfgNodeRead(FPOS);
  287. static VOID FN_L CfgNodeReset(void);
  288. static BOOL FN_L CfgUnlock(void);
  289.  
  290.  
  291.  
  292. //----------------------------------------------------------------------------
  293. //   Description:    Compute size of data area of a block.
  294. //    Parameters:    lSize        Total size of block
  295. //       Returns:    Size of blocks data area.
  296. //----------------------------------------------------------------------------
  297. static LONG FN_L CfgBlockDataSize(LONG lSize) 
  298. {
  299.     Assert(lSize > (LONG)sizeof(CFGBLK));
  300.     return lSize - (LONG)sizeof(CFGBLK);
  301. }
  302.  
  303.  
  304. //----------------------------------------------------------------------------
  305. //   Description:    Check if a block address is valid. Address must be past 
  306. //                          the end of the file header and less than the end of the
  307. //                        file. The address must also be a multiple of BLK_SIZE.
  308. //    Parameters:    fpos            Address of block
  309. //       Returns:    Aborts if address is invalid
  310. //----------------------------------------------------------------------------
  311. #if COMPILE_DEBUG
  312. static VOID FN_L CfgBlockIsAddrValid(FPOS fpos) 
  313. {
  314.     FPOS fsize = FileGetSize(pg->hf);    // Get file size
  315.     if (fsize < 0L)
  316.         Fatal("Internal configuration file error.\nProblem reading file size.");
  317.  
  318.     if (fpos >= fsize)                        // Address past end of file
  319.         Fatal("Internal configuration file error.\nRead/write past end of file.");
  320.                                                     // Address before beginning of file
  321.     if (fpos < (FPOS)sizeof(CFGHDR))
  322.         Fatal("Internal configuration file error.\nRead/write before beginning of file.");
  323.  
  324.     if (fpos % (FPOS)BLK_SIZE)                // Address not on a correct boundary
  325.         Fatal("Internal configuration file error.\nRead/write not on correct boundary.");
  326.  
  327.     return ;
  328. }
  329. #endif
  330.  
  331.  
  332. //----------------------------------------------------------------------------
  333. //   Description:    Mark block as empty. This block is combined with all
  334. //                          empty blocks following immediately before and after it.
  335. //                        The empty space in the block is zeroed for security reasons.
  336. //                        The empty block is then inserted at the start of a linked
  337. //                        list of empty blocks.
  338. //    Parameters:    fpos      Offset (in bytes) of block within the file.
  339. //       Returns:    TRUE if successful.
  340. //----------------------------------------------------------------------------
  341. static BOOL FN_L CfgBlockMarkEmpty(FPOS fpos) 
  342. {
  343.     CFGBLK cfgblk1, cfgblk2;
  344.     FPOS fsize;
  345.     FPOS fposEmpty;
  346.     LONG lData;
  347.  
  348.  
  349.     fsize = FileGetSize(pg->hf);            // Get file size
  350.     if (fsize < 0)
  351.         return FALSE;
  352.  
  353.    //
  354.    // Read current block header.
  355.    //    Read next block header. If block is empty, add it to this block. Keep
  356.    //    going until all adjacent empty blocks are concatenated.
  357.    //
  358.     if (!CfgBlockRead(fpos, &cfgblk1))
  359.         return FALSE;
  360.  
  361.     while (fpos + cfgblk1.lSize < fsize)
  362.         {                                            // Read next block header
  363.         if (!CfgBlockRead(fpos + cfgblk1.lSize, &cfgblk2))
  364.             return FALSE;
  365.  
  366.         if (!cfgblk2.fEmpty)                    // If it's used, break
  367.             break;
  368.                                                     // Otherwise, remove from empty chain
  369.         if (!CfgBlockUseEmpty(fpos + cfgblk1.lSize))
  370.             return FALSE;
  371.  
  372.         cfgblk1.lSize += cfgblk2.lSize;    // Combine with current block
  373.         }
  374.  
  375.    //
  376.    //    At this point, we know that any empty blocks immediately after the
  377.    //    current block have been combined. We must now search for a block
  378.    //    immediately before. If this is not done, the file eventually becomes
  379.    //    a series of minimum sized empty blocks.
  380.     //
  381.    //    NOTE: We only search for ONE preceeding block since that is all
  382.    //            there can be.
  383.    //
  384.     fposEmpty = pg->cfghdr.fposEmpty;
  385.     while (fposEmpty)
  386.         {                                            // Read block
  387.         if (!CfgBlockRead(fposEmpty, &cfgblk2))
  388.             return FALSE;
  389.                                                     // Check if this is the block
  390.         if (fposEmpty + cfgblk2.lSize == fpos)
  391.             {
  392.             LONG lData;                            // Yeah! Combine with new empty block
  393.  
  394.             cfgblk2.lSize += cfgblk1.lSize;
  395.             lData = CfgBlockDataSize(cfgblk2.lSize);
  396.                                                     // Zero out data area
  397.             if (!FileFill(pg->hf, lData, fposEmpty + (FPOS)sizeof(CFGBLK), 0))
  398.                 return FALSE;
  399.                                                     // Write changed block header
  400.             if (!CfgBlockWrite(fposEmpty, &cfgblk2))
  401.                 return FALSE;
  402.  
  403.             return TRUE;
  404.             }
  405.         fposEmpty = cfgblk2.fposNext;        // Goto next block
  406.         }
  407.     //
  408.     //    Else, link this block into the empty block chain.
  409.     //
  410.     cfgblk1.lData = 0;
  411.     cfgblk1.crcData = 0L;
  412.     cfgblk1.fEmpty = TRUE;
  413.     cfgblk1.fposPrev = 0L;
  414.     cfgblk1.fposNext = pg->cfghdr.fposEmpty;
  415.                                                     // Zero out data area
  416.     lData = CfgBlockDataSize(cfgblk1.lSize);
  417.     if (!FileFill(pg->hf, lData, fpos + (FPOS)sizeof(CFGBLK), 0))
  418.         return FALSE;
  419.                                                     
  420.     if (!CfgBlockWrite(fpos, &cfgblk1))    // Write block header 
  421.         return FALSE;
  422.  
  423.     pg->cfghdr.fposEmpty = fpos;            // Change link in file header
  424.     pg->fHeaderDirty = TRUE;
  425.  
  426.     if (cfgblk1.fposNext)                    // Adjust pointer in next block
  427.         {
  428.         if (!CfgBlockRead(cfgblk1.fposNext, &cfgblk2))
  429.             return FALSE;
  430.  
  431.         cfgblk2.fposPrev = fpos;
  432.  
  433.         if (!CfgBlockWrite(cfgblk1.fposNext, &cfgblk2))
  434.             return FALSE;
  435.         }
  436.     return TRUE;
  437. }
  438.  
  439.  
  440. //----------------------------------------------------------------------------
  441. //   Description:    Read data block header.
  442. //    Parameters:    fpos     Offset (in bytes) of block within the file.
  443. //                        pcfgblk    Buffer for block header.
  444. //       Returns:    TRUE if successful.
  445. //----------------------------------------------------------------------------
  446. static BOOL FN_L CfgBlockRead(FPOS fpos, PCFGBLK pcfgblk)
  447. {
  448. #if COMPILE_DEBUG
  449.     CfgBlockIsAddrValid(fpos);                // Validate address
  450. #endif
  451.                                                     // Read block header
  452.     if (!FileRead(pg->hf, pcfgblk, sizeof(CFGBLK), fpos))
  453.         return FALSE;
  454.  
  455. #if COMPILE_DEBUG                                // Check for valid block sizes
  456.     if (pcfgblk->lSize < (LONG)sizeof(CFGBLK)
  457.     || (pcfgblk->lSize % BLK_SIZE)
  458.     || pcfgblk->lData < 0
  459.     ||    pcfgblk->lData + (LONG)sizeof(CFGBLK) > pcfgblk->lSize)
  460.         Fatal("Internal configuration file error.\nCorrupt block header.");
  461. #endif
  462.  
  463.     return TRUE;
  464. }
  465.  
  466.  
  467. //----------------------------------------------------------------------------
  468. //   Description:    Compute the size of block needed to hold the requested 
  469. //                          amount of data. Block size is always rounded to a 
  470. //                        multiple of BLK_SIZE.
  471. //    Parameters:    lData                Data size.
  472. //       Returns:    Required size of data block
  473. //----------------------------------------------------------------------------
  474. static LONG FN_L CfgBlockTotalSize(LONG lData) 
  475. {
  476.     Assert(lData >= 0);
  477.     lData += (LONG)sizeof(CFGBLK);        // Add size of block header
  478.     if (lData % (LONG)BLK_SIZE)            // Round to multiple of block size
  479.         lData += BLK_SIZE - (lData % (LONG)BLK_SIZE);
  480.     return lData;
  481. }
  482.  
  483.  
  484. //----------------------------------------------------------------------------
  485. //   Description:    Use an empty block.
  486. //                          Remove it from the chain of empty blocks.
  487. //    Parameters:    fpos     Offset (in bytes) of block within the file.
  488. //       Returns:    TRUE if successful.
  489. //----------------------------------------------------------------------------
  490. static BOOL FN_L CfgBlockUseEmpty(FPOS fpos)
  491. {
  492.     CFGBLK cfgblk;
  493.  
  494.     if (!CfgBlockRead(fpos, &cfgblk))    // Read the block
  495.         return FALSE;
  496.  
  497.     if (cfgblk.fposPrev)                        // Read previous if not header
  498.         {
  499.         CFGBLK cfgblkPrev;
  500.  
  501.         if (!CfgBlockRead(cfgblk.fposPrev, &cfgblkPrev))
  502.             return FALSE;
  503.                                                     // Update it's link
  504.         cfgblkPrev.fposNext = cfgblk.fposNext;    
  505.         if (!CfgBlockWrite(cfgblk.fposPrev, &cfgblkPrev))
  506.             return FALSE;
  507.         }
  508.     else                                            // Otherwise, update file header
  509.         {                                            // to point to next empty block
  510.         pg->cfghdr.fposEmpty = cfgblk.fposNext;
  511.         pg->fHeaderDirty = TRUE;
  512.         }
  513.  
  514.     if (cfgblk.fposNext)                        // Read next block and update it
  515.         {
  516.         CFGBLK cfgblkNext;
  517.  
  518.         if (!CfgBlockRead(cfgblk.fposNext, &cfgblkNext))
  519.             return FALSE;
  520.                                                     // Update pointer
  521.         cfgblkNext.fposPrev = cfgblk.fposPrev;
  522.                                                     // Write block
  523.         if (!CfgBlockWrite(cfgblk.fposNext, &cfgblkNext))
  524.             return FALSE;
  525.         }
  526.  
  527.     cfgblk.fEmpty = FALSE;                    // Update block, mark used
  528.     cfgblk.fposNext = 0;
  529.     cfgblk.fposPrev = 0;
  530.     if (!CfgBlockWrite(fpos, &cfgblk))    // Write changes
  531.         return FALSE;
  532.  
  533.     return TRUE;
  534. }
  535.  
  536.  
  537. //----------------------------------------------------------------------------
  538. //   Description:    Walk all the blocks in a file in an attempt to
  539. //                          validate the file. 
  540. //    Parameters:    fShow        If TRUE, display blocks to output as they are 
  541. //                                    walked.
  542. //       Returns:    Aborts on error.
  543. //----------------------------------------------------------------------------
  544. #if COMPILE_DEBUG
  545. static VOID FN_L CfgBlockWalk(BOOL fShow)
  546. {
  547.     FPOS fsize = FileGetSize(pg->hf);
  548.     FPOS fpos = (FPOS)sizeof(CFGHDR);
  549.     CFGBLK cfgblk;
  550.  
  551.     if (fsize < (FPOS)sizeof(CFGHDR))
  552.         Fatal("Internal configuration file error.\nFile size is too small.");
  553.  
  554.     while (fpos < fsize)                        // Walk through data blocks
  555.         {
  556.         if (!CfgBlockRead(fpos, &cfgblk))// Read header
  557.             return ;
  558.  
  559.         if (fShow)                                // Show data
  560.             Output("Offset: %8ld  Size: %8ld", fpos, cfgblk.lSize);
  561.  
  562.         if (!cfgblk.fEmpty)                    // If not an empty block, read the 
  563.             {                                        //  data and verify the CRC
  564.             PBYTE pb = NULL;
  565.             SIZET cb = 0;
  566.             CRC crcData;
  567.  
  568.             if (!CfgDataRead(fpos, &cfgblk, &pb, &cb))
  569.                 return ;
  570.  
  571.             crcData = CrcCalc(pb, cb);        // Calc CRC
  572.             MemFree(pb);                        // Free buffer
  573.  
  574.             if (fShow)
  575.                 Output("   Data: (0x%08lX) Length = %ld\n", crcData, cfgblk.lData);
  576.                                                     // Check CRC codes
  577.             if (crcData != cfgblk.crcData)
  578.                 Fatal("Internal configuration file error.\nCRC does not verify.");
  579.             }
  580.         else if (fShow)
  581.             {
  582.             Output("  **EMPTY**  Next: %8ld  Prev: %8ld\n", cfgblk.fposNext, cfgblk.fposPrev);
  583.             }
  584.         fpos += (FPOS)cfgblk.lSize;
  585.         }
  586.  
  587.     // We MUST end up exactly at the end of the file.
  588.     if (fpos > fsize)        
  589.         Fatal("Internal configuration file error.\nFile size is invalid.");
  590.  
  591.     return ;
  592. }
  593. #endif
  594.  
  595.  
  596. //----------------------------------------------------------------------------
  597. //   Description:    Write a block header to the file.
  598. //    Parameters:    fpos        Address to write block at. 
  599. //                        pcfgblk        Pointer to block header to write.
  600. //       Returns:    TRUE if successful.
  601. //----------------------------------------------------------------------------
  602. static FPOS FN_L CfgBlockWrite(FPOS fpos, PCFGBLK pcfgblk) 
  603. {
  604. #if COMPILE_DEBUG
  605.     CfgBlockIsAddrValid(fpos);                // Validate address
  606. #endif
  607.                                                     // Read block header
  608.     if (!FileWrite(pg->hf, pcfgblk, sizeof(CFGBLK), fpos))
  609.         return FALSE;
  610.  
  611.     pg->fHeaderDirty = TRUE;                // Mark header as dirty to update rev
  612.     return TRUE;
  613. }
  614.  
  615.  
  616. //----------------------------------------------------------------------------
  617. //   Description:    Find or create an empty cache buffer.
  618. //                          This function will never return a cache buffer allocated
  619. //                        to one of the nodes in the current search path because they
  620. //                        will have the highest usage counts.
  621. //    Parameters:    pc        Variable to receive offset of empty cache buffer
  622. //       Returns:    TRUE if successful.
  623. //----------------------------------------------------------------------------
  624. static BOOL FN_L CfgCacheFindEmpty(PSIZET pc)
  625. {
  626.     SIZET i;
  627.     LONG lMin;
  628.     BOOL fFound = FALSE;
  629.  
  630.     for (i = 0; i < MAX_CACHE; ++i)        // Look for unused cache buffer
  631.         if (!pg->afCacheUsed[i])
  632.             {
  633.             if (pg->apbCache[i] == NULL)    // If necessary, allocate cache buffer
  634.                 {
  635.                 pg->apbCache[i] = (PBYTE)MemAlloc(NODE_SIZE);
  636.                 if (pg->apbCache[i] == NULL)
  637.                     return FALSE;
  638.                 }
  639.             *pc = i;
  640.             fFound = TRUE;
  641.             break;
  642.             }
  643.     if (!fFound)
  644.         {
  645.        lMin = pg->alCacheUse[0];            // Find cache element with the lowest 
  646.        *pc = 0;                                    // usage count. It is the oldest
  647.        for (i = 1; i < MAX_CACHE; ++i)
  648.            if (pg->alCacheUse[i] < lMin)
  649.                {
  650.                lMin = pg->alCacheUse[i];
  651.                *pc = i;
  652.                }
  653.         if (pg->afCacheDirty[*pc])            // If cache is dirty, flush it.
  654.             if (!CfgCacheFlush(*pc))        // This probable shouldn't ever happen
  655.                 return FALSE;
  656.         }
  657.  
  658. #if COMPILE_DEBUG                                // Double check cache usage!
  659.     for (i = 0; i < pg->cLevel; ++i)
  660.         if (pg->acLevelCache[i] == *pc)
  661.             Fatal("Internal configuration file error.\nCache is corrupted.");
  662. #endif
  663.  
  664.  
  665.     pg->afCacheUsed[*pc] = TRUE;            // Already marked as used!
  666.     pg->afCacheDirty[*pc] = FALSE;
  667.     pg->afposCache[*pc] = (FPOS)-1L;
  668.     pg->alCacheUse[*pc] = ++pg->lCacheUse;
  669.     return TRUE;
  670. }
  671.  
  672.  
  673. //----------------------------------------------------------------------------
  674. //   Description:    Flush a single cache buffer
  675. //                          Assumes that cache is valid and dirty
  676. //    Parameters:    n        Node to flush.     
  677. //       Returns:    TRUE if successful.
  678. //----------------------------------------------------------------------------
  679. static BOOL FN_L CfgCacheFlush(SIZET n)
  680. {
  681.     PBYTE pb = pg->apbCache[n];
  682.  
  683.     pg->afposCache[n] = CfgDataWrite(pg->afposCache[n], (PVOID)pb, NODE_SIZE);
  684.     if (pg->afposCache[n] <= 0)
  685.        return FALSE;
  686.  
  687.     pg->afCacheDirty[n] = FALSE;            // Cache is not dirty anymore
  688.     return TRUE;
  689. }
  690.  
  691.  
  692. //----------------------------------------------------------------------------
  693. //   Description:    Flush all cache buffers
  694. //    Parameters:
  695. //       Returns:    TRUE if successful.
  696. //----------------------------------------------------------------------------
  697. static BOOL FN_L CfgCacheFlushAll(void)
  698. {
  699.     BOOL fResult = TRUE;
  700.     SIZET i;
  701.  
  702.     for (i = 0; i < MAX_CACHE; ++i)
  703.         if (pg->afCacheUsed[i] && pg->afCacheDirty[i])
  704.             if (!CfgCacheFlush(i))
  705.                 fResult = FALSE;
  706.  
  707.     return fResult;
  708. }
  709.  
  710.  
  711. //----------------------------------------------------------------------------
  712. //   Description:    Invalidate cache contents
  713. //    Parameters:    
  714. //       Returns:    TRUE if successful.
  715. //----------------------------------------------------------------------------
  716. static VOID FN_L CfgCacheInvalidate(void)
  717. {
  718.     SIZET i;
  719.  
  720.     for (i = 0; i < MAX_CACHE; ++i)
  721.         {
  722.        pg->afCacheUsed[i] = FALSE;
  723.        pg->afCacheDirty[i] = FALSE;
  724.        pg->afposCache[i] = (ULONG)-1;
  725.        pg->alCacheUse[i] = 0;
  726.         }
  727.     pg->lCacheUse = 0;                        // Reset usage count
  728.     return ;
  729. }
  730.  
  731.  
  732. //----------------------------------------------------------------------------
  733. //   Description:    Shrink the number of allocated cache buffers. This is done
  734. //                          for DOS only!
  735. //    Parameters:    
  736. //       Returns:    TRUE if successful.
  737. //----------------------------------------------------------------------------
  738. static BOOL FN_L CfgCacheShrink(void)
  739. {
  740.     SIZET i;
  741.     for (i = pg->cCacheMin; i < MAX_CACHE; ++i)
  742.         if (pg->apbCache[i])
  743.             {
  744.             MemFree(pg->apbCache[i]);
  745.             pg->apbCache[i] = NULL;
  746.             pg->afCacheUsed[i] = FALSE;
  747.             }
  748.     return TRUE;
  749. }
  750.  
  751.  
  752. //----------------------------------------------------------------------------
  753. //   Description:    Close a configuration file. 
  754. //    Parameters:    
  755. //       Returns:    TRUE if successful.
  756. //----------------------------------------------------------------------------
  757. BOOL FN_E CfgClose(void)
  758. {
  759.     BOOL fResult = TRUE;
  760.     if (pg && pg->fInit)
  761.         {
  762.         SIZET i;
  763.  
  764.         Assert(pg->cLock == 0);                // File should not be locked!
  765.  
  766.         if (!CfgUnlock())                        // Unlock the file
  767.             fResult = FALSE;
  768.  
  769.         for (i = 0; i < MAX_CACHE; ++i)    // Free cache buffers
  770.             if (pg->apbCache[i])
  771.                 MemFree(pg->apbCache[i]);
  772.               
  773. #if OS_OS2 || OS_PM
  774.         if (pg->fSemaphore)                    // Free semaphore
  775.             DosCloseMutexSem(pg->hmtx);
  776. #endif
  777.  
  778.         if (pg->hf >= 0)                        // Close file
  779.             if (!FileClose(pg->hf))
  780.                 fResult = FALSE;
  781.  
  782.         memset(pg, 0, sizeof(CFGGBL));    // Clear data structure
  783.         }
  784.     return fResult;
  785. }
  786.  
  787.  
  788. //----------------------------------------------------------------------------
  789. //   Description:    Create a new configuration file.
  790. //                          File is closed after being created.
  791. //                        File is opened in exclusive access mode to prevent changes
  792. //                        by other processes.
  793. //    Parameters:    pcszFile        Name of file
  794. //       Returns:    TRUE if successful.
  795. //----------------------------------------------------------------------------
  796. static BOOL FN_L CfgCreate(PCSZ pcszFile)
  797. {
  798.     FLAG32 fl = FL_OPEN|FL_CREATE|FL_READWRITE|FL_DENYNONE|FL_BINARY;
  799.     time_t now = time(NULL);
  800.     struct tm *tm = localtime(&now);
  801.  
  802.     //
  803.     //    Verify file header is HDR_SIZE bytes in length
  804.     //
  805.     Assert(sizeof(CFGHDR) == HDR_SIZE);
  806.     Assert((sizeof(CFGHDR) % BLK_SIZE) == 0);
  807.     //
  808.     //    Verify external maximum name length versus internal length
  809.     //
  810.     Assert(MAX_CFG_NAME == KEY_NAME);
  811.     //
  812.     //    An integral number of keys must fit in a node and a node must have
  813.     //    space for at least 3 keys. This is so that a split always has enough
  814.     //    keys for a parent and two children. Splits always occur when a node
  815.     //    is full.
  816.     //
  817.     Assert((NODE_SIZE % KEY_SIZE) == 0);
  818.     Assert(NODE_SIZE / KEY_SIZE >= 3);
  819.     Assert((NODE_SIZE % BLK_SIZE) == 0);
  820.     //
  821.     //    Create a file header.    
  822.     //
  823.     memset(&pg->cfghdr, 0, sizeof(pg->cfghdr));    
  824.     sprintf(pg->cfghdr.szText,                    
  825.         szHeaderFormat,
  826.         (int)((HDR_VER >> 8) & 0x00FF),
  827.         (int)(HDR_VER & 0x00FF),
  828.         CfgGet(CFG_COPYRIGHT, NULL),
  829.         tm->tm_hour, tm->tm_min, tm->tm_sec,
  830.         tm->tm_mon + 1, tm->tm_mday, tm->tm_year + 1900,
  831.         CommandLineAppName());
  832.  
  833.     pg->cfghdr.lId = HDR_ID;                        // Set id and version
  834.     pg->cfghdr.usVer = HDR_VER;
  835.     pg->cfghdr.fposRoot = (FPOS)sizeof(CFGHDR);
  836.     //
  837.     //    Write the file and an empty block for the root node
  838.     //    apbCache[0] is gauranteed to be allocated at this time and is obviously
  839.     //     not in use. It is used as a temporary data area for the root node.
  840.     //
  841.     if (FileOpen(&pg->hf, pcszFile, fl, NULL))
  842.         {
  843.         memset(pg->apbCache[0], 0, NODE_SIZE);
  844.         if (CfgHeaderWrite(TRUE)
  845.         && CfgDataWrite((ULONG)-1L, pg->apbCache[0], NODE_SIZE) > 0)
  846.             {
  847.             FileClose(pg->hf);
  848.             pg->hf = -1;
  849.             Log(LOG_MEDIUM, "Created configuration file.\n'%s'", pcszFile);
  850.             return TRUE;
  851.             }
  852.         }
  853.     pg->hf = -1;
  854.     FileClose(pg->hf);
  855.     return FALSE;
  856. }
  857.  
  858.  
  859. //----------------------------------------------------------------------------
  860. //   Description:    Read data from block
  861. //    Parameters:    fpos        Offset (in bytes) of block within the file.
  862. //                        pcfgblk        Block header.
  863. //                                        If NULL, the block header is read in.
  864. //                        ppb            Variable to receive address of data.
  865. //                                        If null, a buffer is    allocated for the data.
  866. //                                        The caller is responsible for freeing the data
  867. //                                        buffer.
  868. //                        pcb            Variable to receive size of data.
  869. //                                        If not null, the initial value of this variable
  870. //                                        should be the size of the existing data data buffer 
  871. //                                        or 0 for the maximum size.
  872. //                                        If null, size is not returned.
  873. //       Returns:    TRUE if successful.
  874. //----------------------------------------------------------------------------
  875. static BOOL FN_L CfgDataRead(FPOS fpos, PCFGBLK pcfgblk, PBYTE _FAR_ *ppb, PSIZET pcb)
  876. {
  877.     CFGBLK cfgblk;
  878.     SIZET cb;
  879.     BOOL fAlloc = FALSE;
  880.  
  881.     if (pcfgblk == NULL)                        // Read block in if needed
  882.         {
  883.         pcfgblk = &cfgblk;
  884.         if (!CfgBlockRead(fpos, &cfgblk))
  885.             return FALSE;
  886.         }
  887.     cb = (SIZET)pcfgblk->lData;                // Allocate buffer for data
  888.     if (pcb && *pcb)
  889.         cb = MIN(*pcb, cb);
  890.  
  891.     Assert(cb);
  892.     if (*ppb == NULL)                            // Allocate a buffer
  893.         {
  894.         *ppb = (PBYTE)MemAlloc(cb);
  895.         if (*ppb == NULL)
  896.             return FALSE;
  897.         fAlloc = TRUE;
  898.         }
  899.     fpos += sizeof(CFGBLK);                    // Read data
  900.     if (!FileRead(pg->hf, *ppb, cb, fpos))
  901.         {
  902.         if (fAlloc)                                // Free buffer if we allocated it
  903.             MemFree(*ppb);
  904.         return FALSE;
  905.         }
  906.     if (pcb)                                        // Return size
  907.         *pcb = cb;
  908.     return TRUE;
  909. }
  910.  
  911.  
  912. //----------------------------------------------------------------------------
  913. //   Description:    Write a block to the file.
  914. //    Parameters:    fpos        Address to write block at. 
  915. //                                        If fpos <= 0L, find an unused block and write 
  916. //                                        the data to it.
  917. //                            If fpos > 0, check the current size of the
  918. //                            block, and if we don't fit, find a newer, larger
  919. //                            block. Else, just use the current block.
  920. //                pvData      Pointer to data buffer
  921. //                lData       Size of the data to write.
  922. //                                        May not == 0
  923. //       Returns:    Position block was written at, or -1 for error.
  924. //----------------------------------------------------------------------------
  925. static FPOS FN_L CfgDataWrite(FPOS fpos, PCVOID pvData, LONG lData)
  926. {
  927.     CFGBLK cfgblk;
  928.     FPOS fsize = FileGetSize(pg->hf);
  929.     LONG lNeeded;
  930.  
  931.     if (fsize < (FPOS)sizeof(CFGHDR))
  932.         Fatal("Internal configuration file error.\nFile size is too small.");
  933.  
  934.     Assert(pvData && lData);
  935.     lNeeded = CfgBlockTotalSize(lData);    // Get required block size
  936.    //
  937.    //    Read current block. If we can't fit into the current block, mark it
  938.    //     as empty.
  939.    //
  940.     if (fpos > 0L)
  941.         {
  942.         if (!CfgBlockRead(fpos, &cfgblk))
  943.             return (ULONG)-1L;
  944.  
  945.         if (cfgblk.lSize < lNeeded)        // If it doesn't fit,
  946.             {                                        // Mark as empty 
  947.             if (!CfgBlockMarkEmpty(fpos))    
  948.                 return (ULONG)-1L;
  949.  
  950.             fpos = (ULONG)-1L;                            // Set position to -1 to force search
  951.             }                                        //  for new location
  952.         }
  953.    //
  954.    //    Search for an unused block.
  955.    //
  956.     if (fpos <= 0L && pg->cfghdr.fposEmpty)
  957.        {
  958.        fpos = pg->cfghdr.fposEmpty;        // Search the list of empties
  959.        while (fpos)
  960.            {
  961.            if (!CfgBlockRead(fpos, &cfgblk))
  962.                return (FPOS)-1L;
  963.                                                    
  964.            if (cfgblk.lSize >= lNeeded)    // Check if it will fit here
  965.                {                                    // Remove from empty chain
  966.                if (!CfgBlockUseEmpty(fpos))
  967.                    return FALSE;
  968.    
  969.                break;
  970.                }
  971.            fpos = cfgblk.fposNext;            // Move to next empty block
  972.            }
  973.        }
  974.    //
  975.    //    Allocate an empty block at the end of the file.
  976.    //
  977.     if (fpos <= 0L)
  978.         {
  979.         if (!FileSetSize(pg->hf, (FPOS)(fsize + (FPOS)lNeeded)))
  980.             return (FPOS)-1L;
  981.  
  982.         pg->fHeaderDirty = TRUE;            // File is now changed!
  983.                                                     // Lengthen file.
  984.         memset(&cfgblk, 0, sizeof(cfgblk));
  985.         cfgblk.lSize = lNeeded;
  986.         fpos = fsize;                            // Set position and new file size 
  987.         fsize += lNeeded;
  988.         }
  989.    //
  990.    //    If this block is extra long, divide it in two pieces. The second
  991.    //    chunk is an empty block. Leave a little free space at the end of this
  992.     //    block in case it grows.
  993.     //
  994.     lNeeded += 4 * BLK_SIZE;
  995.     if (lNeeded  < cfgblk.lSize)
  996.         {
  997.         CFGBLK cfgblkEmpty;
  998.         FPOS fposEmpty = fpos + lNeeded;
  999.                                                     // Write a block header
  1000.         memset(&cfgblkEmpty, 0, sizeof(cfgblkEmpty));
  1001.         cfgblkEmpty.lSize = cfgblk.lSize - lNeeded;
  1002.                                                     // Write temporary block header
  1003.         if (!CfgBlockWrite(fposEmpty, &cfgblkEmpty))
  1004.             return (FPOS)-1L;
  1005.                                                     // Then mark block as empty
  1006.         if (!CfgBlockMarkEmpty(fposEmpty))
  1007.             return (FPOS)-1L;
  1008.  
  1009.         cfgblk.lSize = lNeeded;                // Adjust size of current block
  1010.         }
  1011.    //
  1012.    //    Now, write the data. 
  1013.     //    A CRC is saved along with each block to validate it.
  1014.    //
  1015.     cfgblk.lData = lData;
  1016.     cfgblk.fEmpty = FALSE;
  1017.     cfgblk.crcData = CrcCalc((PBYTE)pvData, (SIZET)lData);
  1018.     cfgblk.fposPrev = 0L;
  1019.     cfgblk.fposNext = 0L;
  1020.  
  1021.     if (!CfgBlockWrite(fpos, &cfgblk))    // Write header
  1022.         return (FPOS)-1L;
  1023.                                                     // Write data
  1024.     if (!FileWrite(pg->hf, pvData, (SIZET)lData, fpos + (FPOS)sizeof(CFGBLK)))
  1025.         return (FPOS)-1L;
  1026.  
  1027.     pg->fHeaderDirty = TRUE;                // Mark header as dirty to update rev
  1028.  
  1029.     return fpos;
  1030. }
  1031.  
  1032.  
  1033. //----------------------------------------------------------------------------
  1034. //   Description:    Delete an object from a configuration file.
  1035. //    Parameters:    pcszName            Name of the object to write.
  1036. //       Returns:    TRUE if successful.
  1037. //----------------------------------------------------------------------------
  1038. BOOL FN_E CfgDelete(PCSZ pcszName)
  1039. {
  1040.     PCFGKEY pcfgkey;
  1041.     BOOL fResult = FALSE;
  1042.  
  1043.     Assert(pcszName && strlen(pcszName) <= KEY_NAME);
  1044.  
  1045.     if (!CfgLock())
  1046.        return FALSE;
  1047.  
  1048.     if (CfgKeyFind((PSZ)pcszName, &pcfgkey))
  1049.         if (pcfgkey != NULL)
  1050.             {
  1051.             if (CfgKeyDelete())
  1052.                 fResult = TRUE;
  1053.             }
  1054.         else
  1055.             Error("Configuration file key not found\n'%s'", pcszName);
  1056.  
  1057.     if (!CfgUnlock())
  1058.         fResult = FALSE;
  1059.  
  1060.     return fResult;
  1061. }
  1062.  
  1063.  
  1064. //----------------------------------------------------------------------------
  1065. //   Description:    Find a object in the configuration file.
  1066. //    Parameters:    pcszName        Key name to find.
  1067. //       Returns:    TRUE if successful and key found.
  1068. //----------------------------------------------------------------------------
  1069. BOOL FN_E CfgFind(PCSZ pcszName)
  1070. {
  1071.     PCFGKEY pcfgkey;
  1072.     BOOL fResult = FALSE;
  1073.  
  1074.     Assert(pcszName && strlen(pcszName) <= KEY_NAME);
  1075.  
  1076.     if (!CfgLock())
  1077.        return FALSE;
  1078.  
  1079.     if (CfgKeyFind((PSZ)pcszName, &pcfgkey))
  1080.         if (pcfgkey != NULL)
  1081.             fResult = TRUE;
  1082.  
  1083.     if (!CfgUnlock())
  1084.         fResult = FALSE;
  1085.  
  1086.     return fResult;
  1087. }
  1088.  
  1089.  
  1090. //----------------------------------------------------------------------------
  1091. //   Description:    Terminate current find first/next query. This function 
  1092. //                          releases locks on the database so that others can use it.
  1093. //    Parameters:    
  1094. //       Returns:    TRUE if successful.
  1095. //----------------------------------------------------------------------------
  1096. BOOL FN_E CfgFindClose(void )
  1097. {
  1098.     return CfgUnlock();
  1099. }
  1100.  
  1101.  
  1102. //----------------------------------------------------------------------------
  1103. //   Description:    Find first name in configuration file.
  1104. //                          NOTE: CfgFindClose() must be called at the conclusion of
  1105. //                                a search or other users will not be able to access
  1106. //                                the file!
  1107. //    Parameters:    pszName        Buffer for object name. The buffer should be
  1108. //                                        of size MAX_CFG_NAME + 1.
  1109. //       Returns:    TRUE if successful.
  1110. //----------------------------------------------------------------------------
  1111. BOOL FN_E CfgFindFirst(PSZ pszName)
  1112. {
  1113.     FPOS fposNode = pg->cfghdr.fposRoot;
  1114.     PCFGKEY pcfgkey;
  1115.  
  1116.     Assert(pszName);
  1117.  
  1118.     if (!CfgLock())
  1119.        return FALSE;
  1120.  
  1121.     CfgNodeReset();                            // Start reading at the top
  1122.     while (fposNode)                            // While more nodes to search
  1123.         {                                            // Read this node
  1124.         if (!CfgNodeRead(fposNode))
  1125.             break;
  1126.  
  1127.         pcfgkey = CfgNodeKey(CfgNodeLevel());
  1128.         if (pcfgkey->cb == 0)                // No first key, root is empty
  1129.             break;
  1130.  
  1131.         if (pcfgkey->fposLeft == 0)        // Leaf?
  1132.             {
  1133.             strcpy(pszName, pcfgkey->szName);
  1134.             strcpy(pg->szName, pcfgkey->szName);
  1135.             return TRUE;
  1136.             }
  1137.         fposNode = pcfgkey->fposLeft;        // Work down left side of tree
  1138.         }
  1139.     CfgUnlock();
  1140.     return FALSE;                                // Never gets here
  1141. }
  1142.  
  1143.  
  1144. //----------------------------------------------------------------------------
  1145. //   Description:    Find next name in configuration file.
  1146. //                          NOTE: CfgFindClose() must be called at the conclusion of
  1147. //                                a search or other users will not be able to access
  1148. //                                the file!
  1149. //                                CfgFindClose() is automatically called after the
  1150. //                                last key.
  1151. //    Parameters:    pszName        Buffer for object name. The buffer should be
  1152. //                                        of size MAX_CFG_NAME + 1.
  1153. //       Returns:    TRUE if successful.
  1154. //----------------------------------------------------------------------------
  1155. BOOL FN_E CfgFindNext(PSZ pszName)
  1156. {
  1157.     SIZET cLevel;
  1158.     PCFGKEY pcfgkey;
  1159.     FPOS fpos;
  1160.  
  1161.     Assert(pszName);
  1162.                                          
  1163.     if (pg == NULL                                // File should already be locked
  1164.     || !pg->fInit            
  1165.     || pg->cLock == 0
  1166.     || pg->tid != THREADID
  1167.     || pg->hf < 0)
  1168.         return FALSE;
  1169.                                                     // Find previous name
  1170.     if (!CfgKeyFind(pg->szName, &pcfgkey)
  1171.     || pcfgkey == NULL)
  1172.         goto ERROR_EXIT;
  1173.  
  1174.     cLevel = CfgNodeLevel();                // Get pointers to previous key
  1175.     pcfgkey = CfgNodeKey(cLevel);
  1176.  
  1177.     if (!pcfgkey->fLast)                        // If it wasn't the last key in it's 
  1178.         {                                            //     node, then move to the next key.
  1179.         pcfgkey = CfgKeyNext(pcfgkey);
  1180.         if (pcfgkey->fposLeft)                // If that key has a left child, 
  1181.             {                                        //   recurse into it.
  1182.             FPOS fposNode = pcfgkey->fposLeft;
  1183.           while (fposNode)                            
  1184.               {                                    // Read a level deeper
  1185.               if (!CfgNodeRead(fposNode))
  1186.                     goto ERROR_EXIT;
  1187.                                                     // Find first key in node
  1188.               pcfgkey = CfgNodeKey(CfgNodeLevel());
  1189.               if (pcfgkey->fposLeft == 0)// If it is a leaf, return name
  1190.                     goto SUCCESS_EXIT;
  1191.  
  1192.               fposNode = pcfgkey->fposLeft;
  1193.               }
  1194.             }
  1195.         goto SUCCESS_EXIT;                    // Next key in leaf node
  1196.         }
  1197.     if (pcfgkey->fposRight)                    // Last key, if it has a right child,
  1198.         {                                            //  move down to the child
  1199.         FPOS fposNode = pcfgkey->fposRight;
  1200.        while (fposNode)                        // Then work down the left side of the
  1201.            {                                        //  child's sub-tree
  1202.            if (!CfgNodeRead(fposNode))
  1203.                     goto ERROR_EXIT;
  1204.                                                     // If it's a lead, use it
  1205.            pcfgkey = CfgNodeKey(CfgNodeLevel());
  1206.            if (pcfgkey->fposLeft == 0)        
  1207.                     goto SUCCESS_EXIT;
  1208.  
  1209.            fposNode = pcfgkey->fposLeft;
  1210.            }
  1211.         }
  1212.     do                                                // We are at a right most leaf key
  1213.         {
  1214.         if (!cLevel)
  1215.             goto ERROR_EXIT;
  1216.  
  1217.         fpos = CfgNodePos(cLevel);            // Move up until we are not a right
  1218.         cLevel--;                                //  most leaf key and not the root.
  1219.         pcfgkey = CfgNodeKey(cLevel);
  1220.         }
  1221.     while (fpos == pcfgkey->fposRight);
  1222.  
  1223. SUCCESS_EXIT:                                    // Store names and exit
  1224.     strcpy(pszName, pcfgkey->szName);
  1225.     strcpy(pg->szName, pcfgkey->szName);
  1226.     return TRUE;
  1227.  
  1228. ERROR_EXIT:
  1229.     CfgUnlock();                                // No more keys, release and return
  1230.     return FALSE;
  1231.  
  1232. }
  1233.  
  1234.  
  1235. //----------------------------------------------------------------------------
  1236. //   Description:    Read configuration file header.
  1237. //                          File should already be locked
  1238. //    Parameters:    
  1239. //       Returns:    TRUE if successful.
  1240. //----------------------------------------------------------------------------
  1241. static BOOL FN_L CfgHeaderRead(void)
  1242. {                                                    // Read the header
  1243.     if (!FileRead(pg->hf, (PVOID)&pg->cfghdr, sizeof(pg->cfghdr), 0L))
  1244.         return FALSE;
  1245.  
  1246.     if (pg->cfghdr.lId != HDR_ID)            // Check id and version
  1247.         return Error("Internal configuration file error.\nHeader id is incorrect.");
  1248.  
  1249.     if (pg->cfghdr.usVer != HDR_VER)
  1250.         return Error("Internal configuration file error.\nUnrecognized format.\n.");
  1251.     
  1252.     pg->fHeaderDirty = FALSE;
  1253.                                                     // If file has changed, invalidate cache
  1254.     if (pg->lRevision != pg->cfghdr.lRevision)
  1255.         CfgCacheInvalidate();
  1256.  
  1257.     return TRUE;
  1258. }
  1259.  
  1260.  
  1261. //----------------------------------------------------------------------------
  1262. //   Description:    Write configuration file header if dirty.
  1263. //                          File should already be locked
  1264. //                        A revision counter is updated before writing. This counter 
  1265. //                        is checked when the header is re-read, if it has changed,
  1266. //                        then another process has modified the data file and the
  1267. //                        cache's are invalidated.
  1268. //    Parameters:    fForce        If true, force header to be written.
  1269. //       Returns:    TRUE if successful.
  1270. //----------------------------------------------------------------------------
  1271. static BOOL FN_L CfgHeaderWrite(BOOL fForce)
  1272. {
  1273.     if (pg->fHeaderDirty || fForce)
  1274.         {
  1275.         pg->cfghdr.lRevision++;
  1276.         pg->lRevision = pg->cfghdr.lRevision;
  1277.         if (!FileWrite(pg->hf, (PVOID)&pg->cfghdr, sizeof(pg->cfghdr), 0L))
  1278.             return FALSE;
  1279.         pg->fHeaderDirty = FALSE;
  1280.         }
  1281.     return TRUE;
  1282. }
  1283.  
  1284.  
  1285. //----------------------------------------------------------------------------
  1286. //   Description:    Initialize global variables.
  1287. //    Parameters:    
  1288. //       Returns:    TRUE if successful.
  1289. //----------------------------------------------------------------------------
  1290. static BOOL FN_L CfgInitialize(void)
  1291. {
  1292.     SIZET i;
  1293.  
  1294.     memset(pg, 0, sizeof(CFGGBL));        // Zero just to be safe
  1295.     pg->fInit = TRUE;                            // Set initialize flag right away!
  1296.     pg->hf = -1;
  1297.  
  1298.     pg->cCacheMin = MIN_CACHE;                // Minimum cache size
  1299.  
  1300.     for (i = 0; i < MIN_CACHE; ++i)        // Allocate minimum number of cache 
  1301.         {                                            //  buffers
  1302.         pg->apbCache[i] = (PBYTE)MemAlloc(NODE_SIZE);
  1303.         if (pg->apbCache[i] == NULL)
  1304.             return FALSE;
  1305.         }
  1306.  
  1307. #if OS_OS2 || OS_PM
  1308.     if (DosCreateMutexSem(NULL, &pg->hmtx, 0, 0))
  1309.         return FALSE;
  1310.  
  1311.     pg->fSemaphore = TRUE;
  1312. #endif
  1313.  
  1314.     CfgCacheInvalidate();                    // Invalidate the cache
  1315.     return TRUE;
  1316. }
  1317.  
  1318.  
  1319. //----------------------------------------------------------------------------
  1320. //   Description:    Count the keys on a particular level.
  1321. //    Parameters:    cLevel    Level to count keys in.
  1322. //                        pcb        If not null, the total size of the keys is returned
  1323. //                                    in this variable.
  1324. //       Returns:    Number of keys in node
  1325. //----------------------------------------------------------------------------
  1326. static SIZET FN_L CfgKeyCount(SIZET cLevel, PSIZET pcb)
  1327. {
  1328.     PCFGKEY pcfgkey = (PCFGKEY)CfgNodeBuf(cLevel);
  1329.     SIZET cb = 0;
  1330.     SIZET cKeys = 0;
  1331.  
  1332.     for (;pcfgkey->cb;)                        // Watch for empty root node!
  1333.         {
  1334.         cb += (SIZET)pcfgkey->cb;
  1335.         cKeys++;
  1336.         if (pcfgkey->fLast)
  1337.             break;
  1338.         pcfgkey = CfgKeyNext(pcfgkey);
  1339.         }
  1340.     if (pcb)                                        // Return total size
  1341.         *pcb = cb;
  1342.     return cKeys;
  1343. }
  1344.  
  1345.  
  1346. //----------------------------------------------------------------------------
  1347. //   Description:    Delete a key from a node.
  1348. //                          On entry, the current key has already been found
  1349. //    Parameters:    
  1350. //       Returns:    TRUE if successful.
  1351. //----------------------------------------------------------------------------
  1352. static BOOL FN_L CfgKeyDelete(void)
  1353. {
  1354.     BYTE abKey[2][KEY_SIZE];
  1355.     SIZET cWhich = 0;
  1356.     PCFGKEY pcfgkeyDelete = CfgNodeKey(CfgNodeLevel());
  1357.     PCFGKEY pcfgkeyInsert = NULL;
  1358.     PCFGKEY pcfgkey = pcfgkeyDelete;
  1359.     PCFGKEY pcfgkeySave;
  1360.     SIZET cLevel;
  1361.    FPOS fpos;
  1362.                                                     // While not a leaf node
  1363.     while (pcfgkey->fposLeft || pcfgkey->fposRight)
  1364.         {                                            // Move into child
  1365.         fpos = pcfgkey->fposLeft ? pcfgkey->fposLeft: pcfgkey->fposRight;
  1366.         if (!CfgNodeRead(fpos))
  1367.             return FALSE;
  1368.                                                     // Move to last key in node
  1369.         pcfgkey = CfgNodeKey(CfgNodeLevel());
  1370.         while (!pcfgkey->fLast)
  1371.             pcfgkey = CfgKeyNext(pcfgkey);
  1372.  
  1373.         CfgNodeSetKey(CfgNodeLevel(), pcfgkey);
  1374.         }
  1375.  
  1376.     for (;;)
  1377.         {
  1378.        cLevel = CfgNodeLevel();            // Get pointer to key
  1379.          pcfgkey = CfgNodeKey(cLevel);
  1380.         CfgNodeDirty(cLevel);                // Mark node as dirty
  1381.  
  1382.         if (pcfgkey == pcfgkeyDelete)
  1383.             break;
  1384.  
  1385.         if (pcfgkeyInsert)                    // If node inserted was a right child
  1386.             if (CfgNodePos(cLevel+1) == pcfgkey->fposRight)
  1387.                 {                                    // Move it up to next level
  1388.                 pcfgkey->fposRight = pcfgkeyInsert->fposLeft;
  1389.                 pcfgkeyInsert->fposLeft = CfgNodePos(cLevel);
  1390.                 pg->cLevel--;
  1391.                 continue;
  1392.                 }
  1393.                                                     // Get pointer to buffer
  1394.         pcfgkeySave = (PCFGKEY)abKey[cWhich];
  1395.         cWhich = !cWhich;
  1396.                                                     // Save key
  1397.        memcpy(pcfgkeySave, pcfgkey, (SIZET)pcfgkey->cb);
  1398.  
  1399.        if (!CfgKeyRemove())                    // Remove key from node
  1400.            return FALSE;
  1401.  
  1402.         if (pcfgkeyInsert)
  1403.             {
  1404.             if (pcfgkeySave->fposLeft && pcfgkeySave->fposRight)
  1405.                 pcfgkeyInsert->fposRight = pcfgkeySave->fposRight;
  1406.  
  1407.             if (!CfgKeyInsert(cLevel, pcfgkeyInsert))
  1408.                 return FALSE;
  1409.             }
  1410.  
  1411.         pcfgkeySave->fposRight = 0;
  1412.        if (CfgKeyCount(cLevel, NULL) == 0)
  1413.            {
  1414.             CfgNodeUsed(cLevel, FALSE);
  1415.            if (!CfgBlockMarkEmpty(CfgNodePos(cLevel)))
  1416.                return FALSE;
  1417.  
  1418.             pcfgkeySave->fposLeft = 0;
  1419.            }
  1420.        else
  1421.            {
  1422.            pcfgkeySave->fposLeft = CfgNodePos(cLevel);
  1423.            }
  1424.         pcfgkeyInsert = pcfgkeySave;
  1425.         pg->cLevel--;
  1426.         }
  1427.  
  1428.     if (pcfgkeyInsert)
  1429.         if (pcfgkey->fposLeft && pcfgkey->fposRight)
  1430.             pcfgkeyInsert->fposRight = pcfgkeySave->fposRight;
  1431.  
  1432.     if (pcfgkey->fposData)
  1433.         if (!CfgBlockMarkEmpty(pcfgkey->fposData))
  1434.             return FALSE;
  1435.  
  1436.     if (!CfgKeyRemove())                    // Remove key from node
  1437.         return FALSE;
  1438.  
  1439.     if (pcfgkeyInsert)
  1440.         if (!CfgKeyInsert(cLevel, pcfgkeyInsert))
  1441.             return FALSE;
  1442.  
  1443.     if (CfgKeyCount(cLevel, NULL) > 0 || CfgNodeLevel() == 0)
  1444.         return TRUE;
  1445.  
  1446.     CfgNodeUsed(cLevel, FALSE);
  1447.     if (!CfgBlockMarkEmpty(CfgNodePos(cLevel)))
  1448.         return FALSE;
  1449.  
  1450.     pg->cLevel--;                                // Node was completely empty
  1451.     cLevel = CfgNodeLevel();
  1452.     CfgNodeDirty(cLevel);                    // Remove link from parent
  1453.     pcfgkey = CfgNodeKey(cLevel);
  1454.     if (CfgNodePos(cLevel + 1) == pcfgkey->fposLeft)
  1455.         pcfgkey->fposLeft = 0;
  1456.     else
  1457.         pcfgkey->fposRight = 0;
  1458.  
  1459.     return TRUE;
  1460. }
  1461.  
  1462.  
  1463. //----------------------------------------------------------------------------
  1464. //   Description:    Find a key in the configuration file.
  1465. //    Parameters:    pszName        Key name.
  1466. //                        ppcfgkey        Variable to receive pointer to key
  1467. //                                        This pointer is null if the key value is not 
  1468. //                                        found.
  1469. //       Returns:    TRUE if successful.
  1470. //----------------------------------------------------------------------------
  1471. static BOOL FN_L CfgKeyFind(PCSZ pcszName, PCFGKEY _FAR_ *ppcfgkey)
  1472. {
  1473.     FPOS fposNode = pg->cfghdr.fposRoot;// Start at root
  1474.     PCFGKEY pcfgkey;
  1475.  
  1476.     CfgNodeReset();                            // Reset node level information
  1477.     while (fposNode)                            // While more nodes to search
  1478.         {
  1479.         SIZET cLevel;
  1480.  
  1481.         if (!CfgNodeRead(fposNode))
  1482.             return FALSE;
  1483.  
  1484.         cLevel = CfgNodeLevel();
  1485.         fposNode = 0;
  1486.         pcfgkey = CfgNodeKey(cLevel);
  1487.         for (;pcfgkey->cb;)                    // Watch for root node empty (cb==0)
  1488.             {
  1489.            int rc = strcmp(pcszName, pcfgkey->szName);
  1490.  
  1491.            if (rc == 0)                        // Yeah! A match
  1492.                 {
  1493.                 CfgNodeSetKey(cLevel, pcfgkey);
  1494.                 *ppcfgkey = pcfgkey;
  1495.                return TRUE;
  1496.                 }
  1497.            else if (rc < 0)                    // Less than current key
  1498.                 {
  1499.                fposNode = pcfgkey->fposLeft;
  1500.                 break;
  1501.                 }                                    
  1502.             else if (rc > 0 && pcfgkey->fLast)    // If greater than key and on 
  1503.                 {                                            //  key in node, move down to 
  1504.                 fposNode = pcfgkey->fposRight;    //  right child
  1505.                 break;
  1506.                 }
  1507.             pcfgkey = CfgKeyNext(pcfgkey);
  1508.             }
  1509.         CfgNodeSetKey(cLevel, pcfgkey);
  1510.         }
  1511.     *ppcfgkey = NULL;                            // Not found
  1512.     return TRUE;
  1513. }
  1514.  
  1515.  
  1516. //----------------------------------------------------------------------------
  1517. //   Description:    Insert a key into the b-plus tree node.
  1518. //                          The current node is guaranteed to have sufficient space
  1519. //                        to insert the new key.
  1520. //    Parameters:    cLevel            Level to insert into.
  1521. //                        pcfgkeyInsert    Key to insert. This key is in temporary
  1522. //                                            storage!
  1523. //       Returns:    TRUE if successful and object found.
  1524. //----------------------------------------------------------------------------
  1525. static BOOL FN_L CfgKeyInsert(SIZET cLevel, PCFGKEY pcfgkeyInsert)
  1526. {
  1527.     PCFGKEY pcfgkey = (PCFGKEY)CfgNodeBuf(cLevel);
  1528.     SIZET cb;
  1529.                                              
  1530.     CfgKeyCount(cLevel, &cb);                // Get size of key
  1531.     CfgNodeDirty(cLevel);                    // Cache dirty!
  1532.  
  1533.     for (;pcfgkey->cb;)                        // Check that node not empty
  1534.         {
  1535.         int rc = strcmp(pcfgkeyInsert->szName, pcfgkey->szName);
  1536.  
  1537.         Assert(rc != 0);
  1538.         if (rc < 0)
  1539.             break;
  1540.  
  1541.         cb -= (SIZET)pcfgkey->cb;            // Number of bytes remaining
  1542.  
  1543.         if (pcfgkey->fLast)                    // Insert the split key as the last key
  1544.             {                                        // The node that was split must have been
  1545.             pcfgkey->fposRight = 0;            //  the right child of the last key
  1546.             pcfgkey->fLast = FALSE;
  1547.             pcfgkey = CfgKeyNext(pcfgkey);
  1548.             memcpy(pcfgkey, pcfgkeyInsert, (SIZET)pcfgkeyInsert->cb);
  1549.             pcfgkey->fLast = TRUE;
  1550.             CfgNodeSetKey(cLevel, pcfgkey);
  1551.             return TRUE;
  1552.             }
  1553.         pcfgkey = CfgKeyNext(pcfgkey);
  1554.         }
  1555.     //
  1556.     //    When we get here, we are inserting into the middle of
  1557.     //    a node. The node which was split was the left child of the
  1558.     //    key pointed to by 'pcfgkey'.
  1559.     //
  1560.     if (pcfgkey->cb && pcfgkeyInsert->fposRight)
  1561.         {
  1562.         pcfgkey->fposLeft = pcfgkeyInsert->fposRight;
  1563.         pcfgkeyInsert->fposRight = 0;
  1564.         }
  1565.     pcfgkeyInsert->fLast = FALSE;
  1566.     if (cb)                                        // Make room for new key
  1567.         memmove(((PBYTE)pcfgkey) + (SIZET)pcfgkeyInsert->cb, pcfgkey, (SIZET)cb);
  1568.     else
  1569.         pcfgkeyInsert->fLast = TRUE;        // Very first key in root node!
  1570.                                                     // Copy new key into buffer
  1571.     memcpy(pcfgkey, pcfgkeyInsert, (SIZET)pcfgkeyInsert->cb);
  1572.     CfgNodeSetKey(cLevel, pcfgkey);
  1573.     return TRUE;
  1574. }
  1575.  
  1576.  
  1577. //----------------------------------------------------------------------------
  1578. //   Description:    Add a new key to the configuration file.
  1579. //                          The node being created must not exist!
  1580. //    Parameters:    pcszName        Key name.
  1581. //                        ppcfgkey        Variable to receive pointer to key
  1582. //                        cb                Size of data area for key.
  1583. //       Returns:    TRUE if successful.
  1584. //----------------------------------------------------------------------------
  1585. static BOOL FN_L CfgKeyNew(PCSZ pcszName, PCFGKEY _FAR_ *ppcfgkey, SIZET cb)
  1586. {
  1587.     FPOS fposNode = pg->cfghdr.fposRoot;        
  1588.     PCFGKEY pcfgkey;
  1589.     BYTE bKey[KEY_SIZE];
  1590.     PCFGKEY pcfgkeyNew = (PCFGKEY)bKey;
  1591.     SIZET cLevel;
  1592.  
  1593.     CfgNodeReset();                            // Start reading from the top
  1594.     while (fposNode)
  1595.         {
  1596.         SIZET cNode;
  1597.  
  1598.         if (!CfgNodeRead(fposNode))
  1599.             return FALSE;
  1600.  
  1601.         cLevel = CfgNodeLevel();
  1602.         CfgKeyCount(cLevel, &cNode);
  1603.         Assert(cNode <= NODE_SIZE);
  1604.         if (NODE_SIZE - cNode < KEY_SIZE)// Check if we need to split
  1605.             {
  1606.             if (!CfgKeySplit())                // Split node
  1607.                 return FALSE;
  1608.                                  
  1609.             //
  1610.             //    After a split, simply start reading from the top.
  1611.             //    This is not the most efficient way but it is the simplist.
  1612.             //    It should go fairly fast because all nodes are cached.
  1613.             //    If this we not done, the cache would have to be carefully
  1614.             //    updated to make sure that the node in the search path have
  1615.             //    the highest usage counts -- otherwise they may get discarded.
  1616.             //
  1617.             CfgNodeReset();
  1618.             fposNode = pg->cfghdr.fposRoot;
  1619.             }
  1620.         else
  1621.             {
  1622.             pcfgkey = CfgNodeKey(cLevel);
  1623.             fposNode = 0;
  1624.             if (pcfgkey->cb == 0)            // Root node and it's empty
  1625.                 break;
  1626.  
  1627.             for (;;)                 
  1628.                 {
  1629.                 int rc = strcmp(pcszName, pcfgkey->szName);
  1630.  
  1631.                 Assert(rc != 0);
  1632.                 if (rc < 0)                        // Less than current key
  1633.                     {                                // Move down to left
  1634.                     CfgNodeSetKey(cLevel, pcfgkey);
  1635.                     fposNode = pcfgkey->fposLeft;
  1636.                     break;
  1637.                     }
  1638.                 else if (rc > 0 && pcfgkey->fLast)            // If greater than key and on
  1639.                     {                                                    //  key in node, move down to
  1640.                     CfgNodeSetKey(cLevel, pcfgkey);            //  right child
  1641.                     fposNode = pcfgkey->fposRight;
  1642.                     break;
  1643.                     }
  1644.                 pcfgkey = CfgKeyNext(pcfgkey);
  1645.                 Assert(pcfgkey->cb);
  1646.                 }
  1647.             }                                        
  1648.         }
  1649.     //
  1650.     //    OK, this is where we insert the key. We are in a leaf node.
  1651.     //    Create the key. Leave room for data if possible.
  1652.     //
  1653.     memset(pcfgkeyNew, 0, sizeof(CFGKEY));
  1654.     pcfgkeyNew->cb = KEY_OVERHEAD;
  1655.     strcpy(pcfgkeyNew->szName, pcszName);
  1656.     pcfgkeyNew->cb += strlen(pcszName);
  1657.     pcfgkeyNew->cbData = (LONG)cb;
  1658.                                                     // If data fits, leave room for it
  1659.     if ((ULONG)KEY_SIZE - pcfgkeyNew->cb >= cb)
  1660.         pcfgkeyNew->cb += (LONG)cb;
  1661.  
  1662.     if (!CfgKeyInsert(cLevel, pcfgkeyNew))        // Insert the key, this will also mark
  1663.         return FALSE;                                    //  the node as dirty
  1664.  
  1665.     *ppcfgkey = CfgNodeKey(cLevel);
  1666.     return TRUE;                                
  1667. }                                                    
  1668.  
  1669.  
  1670. //----------------------------------------------------------------------------
  1671. //   Description:    Remove the current key on the current level.
  1672. //                          This routine assumes that more than one key exists.
  1673. //    Parameters:
  1674. //       Returns:    TRUE if successful.
  1675. //----------------------------------------------------------------------------
  1676. static BOOL FN_L CfgKeyRemove(void)
  1677. {
  1678.     SIZET cLevel = CfgNodeLevel();
  1679.     PCFGKEY pcfgkey = CfgNodeKey(cLevel);
  1680.     PCFGKEY pcfgkey1 = (PCFGKEY)CfgNodeBuf(cLevel);
  1681.     PCFGKEY pcfgkey2;
  1682.  
  1683.     CfgNodeDirty(cLevel);
  1684.     if (pcfgkey1 == pcfgkey)                // Key to remove is first in list
  1685.         {
  1686.         if (pcfgkey1->fLast)                    // Key to remove was only key in list
  1687.             {
  1688.             pcfgkey1->cb = 0;
  1689.             return TRUE;
  1690.             }
  1691.         pcfgkey2 = pcfgkey1;
  1692.         pcfgkey1 = CfgKeyNext(pcfgkey1);
  1693.         }
  1694.     else                                            
  1695.         {
  1696.         do
  1697.             {
  1698.             pcfgkey2 = pcfgkey1;
  1699.             pcfgkey1 = CfgKeyNext(pcfgkey1);
  1700.             }
  1701.         while (pcfgkey1 != pcfgkey);
  1702.         if (pcfgkey1->fLast)                    // Key to remove was last in list
  1703.             {
  1704.             pcfgkey2->fLast = TRUE;
  1705.             return TRUE;
  1706.             }
  1707.         pcfgkey2 = pcfgkey1;
  1708.         pcfgkey1 = CfgKeyNext(pcfgkey1);
  1709.         }
  1710.     for (;;)
  1711.         {
  1712.         PCFGKEY pcfgkey12 = CfgKeyNext(pcfgkey1);
  1713.  
  1714.         memmove(pcfgkey2, pcfgkey1, (SIZET)pcfgkey1->cb);
  1715.         if (pcfgkey2->fLast)
  1716.             break;
  1717.  
  1718.         pcfgkey2 = CfgKeyNext(pcfgkey2);
  1719.         pcfgkey1 = pcfgkey12;
  1720.         }
  1721.     return TRUE;
  1722. }
  1723.  
  1724.  
  1725. //----------------------------------------------------------------------------
  1726. //   Description:    Split the current node.
  1727. //    Parameters:    
  1728. //       Returns:    TRUE if successful and object found.
  1729. //----------------------------------------------------------------------------
  1730. static BOOL FN_L CfgKeySplit(void)
  1731. {
  1732.     SIZET i;
  1733.     SIZET cLevel = CfgNodeLevel();
  1734.     SIZET cKeys = CfgKeyCount(cLevel, NULL);
  1735.     SIZET n = pg->acLevelCache[CfgNodeLevel()];
  1736.     SIZET m;
  1737.     SIZET cM;
  1738.     PBYTE pbN = pg->apbCache[n];
  1739.     PBYTE pbM;
  1740.     SIZET root;
  1741.     PBYTE pbRoot;
  1742.     PCFGKEY pcfgkey1, pcfgkey2;
  1743.     PCFGKEY pcfgkeySplit;
  1744.     BYTE bKey[KEY_SIZE];
  1745.  
  1746.     Assert(cKeys >= 3);                        // Must be at least 3 to split!
  1747.  
  1748.     if (!CfgNodeNew(&m))                        // Create a new node to split into
  1749.         return FALSE;
  1750.  
  1751.     pbM = pg->apbCache[m];
  1752.     Assert(pbM != pbN);                        // This should not happen!
  1753.  
  1754.     pg->afCacheDirty[n] = TRUE;            // Mark nodes as dirty
  1755.     pg->afCacheDirty[m] = TRUE;
  1756.  
  1757.     pcfgkey1 = NULL;                            // Skip first cKeys/2 keys
  1758.     pcfgkey2 = (PCFGKEY)pbN;                // They remain in this node
  1759.     for (i = 0; i < cKeys / 2; ++i)        
  1760.         {
  1761.         pcfgkey1 = pcfgkey2;
  1762.         pcfgkey2 = CfgKeyNext(pcfgkey2);
  1763.         }
  1764.  
  1765.     pcfgkeySplit = pcfgkey2;                // Save pointer to key being split
  1766.  
  1767.     pcfgkey1->fLast = TRUE;                    // Update new 'last key' in node N
  1768.     pcfgkey1->fposRight = pcfgkeySplit->fposLeft;
  1769.     pcfgkey2 = CfgKeyNext(pcfgkey2);        // Jump over split key
  1770.  
  1771.     cM = 0;                                        // Copy last (cKeys-1)/2 keys to new node
  1772.     for (i = 0; i < (cKeys-1)/2; ++i)
  1773.         {
  1774.         memcpy(pbM + (SIZET)cM, (PBYTE)pcfgkey2, (SIZET)pcfgkey2->cb);
  1775.         cM += (SIZET)pcfgkey2->cb;
  1776.         pcfgkey2 = CfgKeyNext(pcfgkey2);    // Jump over split key
  1777.         }
  1778.                                                     // Move split key to temp storage
  1779.     memcpy(bKey, pcfgkeySplit, (SIZET)pcfgkeySplit->cb);
  1780.     pcfgkeySplit = (PCFGKEY)bKey;
  1781.     pcfgkeySplit->fposLeft = pg->afposCache[n];
  1782.     pcfgkeySplit->fposRight = pg->afposCache[m];
  1783.  
  1784.     if (CfgNodeLevel() > 0)                    // If not root, insert split key into
  1785.         {                                            //  parent node
  1786.         return CfgKeyInsert(CfgNodeLevel() - 1, pcfgkeySplit);
  1787.         }
  1788.     
  1789.     //
  1790.     //    When we get here, we are splitting the root node. The tree
  1791.     //    will be growing by one level.
  1792.     //
  1793.     if (!CfgNodeNew(&root))                    // Create a new node to split into
  1794.         return FALSE;
  1795.                                                 
  1796.     pg->afCacheDirty[root] = TRUE;        // Node is dirty
  1797.     pbRoot = pg->apbCache[root];            // Get pointer to buffer
  1798.  
  1799.     Assert(pbRoot != pbN);                    // This should not happen!
  1800.     Assert(pbRoot != pbM);
  1801.  
  1802.     pcfgkeySplit->fLast = TRUE;            // Copy key to root
  1803.     memcpy(pbRoot, (PBYTE)pcfgkeySplit, (SIZET)pcfgkeySplit->cb);
  1804.  
  1805.     pg->fHeaderDirty = TRUE;                // Update file header
  1806.     pg->cfghdr.fposRoot = pg->afposCache[root];
  1807.  
  1808.     return TRUE;
  1809. }
  1810.  
  1811.  
  1812. //----------------------------------------------------------------------------
  1813. //   Description:    Lock a configuration file. 
  1814. //    Parameters:    
  1815. //       Returns:    TRUE if successful.
  1816. //----------------------------------------------------------------------------
  1817. static BOOL FN_L CfgLock(void)
  1818. {
  1819.     if (pg == NULL || !pg->fInit)            // Not valid?
  1820.         return FALSE;
  1821.  
  1822.     if (pg->tid == THREADID)                // Nested request?
  1823.         {
  1824.         pg->cLock++;
  1825.         return TRUE;
  1826.         }
  1827.  
  1828. #if OS_OS2 || OS_PM
  1829.     if (DosRequestMutexSem(pg->hmtx, -1))    
  1830.         return FALSE;
  1831. #endif
  1832.  
  1833.     pg->cLock = 1;
  1834.     pg->tid = THREADID;
  1835.  
  1836.     if (!FileLock(pg->hf, (FPOS)-1L, 0))        // Lock the file
  1837.         return FALSE;
  1838.  
  1839.     return CfgHeaderRead();                    // Read header
  1840. }
  1841.  
  1842.  
  1843. //----------------------------------------------------------------------------
  1844. //   Description:    Create a new b-plus tree node.
  1845. //                          The buffer is flushed immediately so that the disk address
  1846. //                        of the node is known.
  1847. //    Parameters:    pc        Buffer to receive cache id of new node
  1848. //       Returns:    TRUE if successful.
  1849. //----------------------------------------------------------------------------
  1850. static BOOL FN_L CfgNodeNew(PSIZET pc)
  1851. {
  1852.     SIZET n;
  1853.  
  1854.     if (!CfgCacheFindEmpty(&n))            // Get an unused cache buffer
  1855.         return FALSE;
  1856.  
  1857.     memset(pg->apbCache[n],0,NODE_SIZE);// Zero
  1858.     *pc = n;                                        // Flush it to disk
  1859.     return CfgCacheFlush(n);
  1860. }
  1861.  
  1862.  
  1863. //----------------------------------------------------------------------------
  1864. //   Description:    Read a b-plus tree node from the disk.
  1865. //                          This node is assumed to be the node for the next level in
  1866. //                        the tree.
  1867. //    Parameters:    fpos        Offset of node.
  1868. //       Returns:    TRUE if successful.
  1869. //----------------------------------------------------------------------------
  1870. static BOOL FN_L CfgNodeRead(FPOS fpos)
  1871. {
  1872.     SIZET i;
  1873.     BOOL fFound = FALSE;
  1874.     PBYTE pb;
  1875.     SIZET cb;
  1876.  
  1877.  
  1878.     Assert(pg->cLevel < MAX_LEVEL);        // Can't read any deeper!
  1879.  
  1880.     for (i = 0; i < MAX_CACHE; ++i)        // Search for node in cache
  1881.         if (pg->afCacheUsed[i] && pg->afposCache[i] == fpos)
  1882.             {
  1883.             fFound = TRUE;                        // Increment usage count!
  1884.             pg->alCacheUse[i] = ++pg->lCacheUse;
  1885.             break;
  1886.             }
  1887.  
  1888.     if (!fFound)
  1889.         {
  1890.         if (!CfgCacheFindEmpty(&i))        // Get an unused cache buffer
  1891.             return FALSE;
  1892.  
  1893.         pg->afposCache[i] = fpos;            // Set cache disk location
  1894.  
  1895.         pb = pg->apbCache[i];                // Read node from disk
  1896.         cb = 0;
  1897.         if (!CfgDataRead(fpos, NULL, &pb, &cb))
  1898.             {
  1899.             pg->afCacheUsed[i] = FALSE;    // Free cache buffer on failure!
  1900.             return FALSE;
  1901.             }
  1902.         Assert(cb == NODE_SIZE);            // Validate size
  1903.         }
  1904.  
  1905.     pg->acLevelCache[pg->cLevel] = i;    // Store level information
  1906.     pg->apcfgkeyLevel[pg->cLevel] = (PCFGKEY)CfgNodeBuf(pg->cLevel);
  1907.     pg->cLevel++;                                // Move to next level
  1908.     return TRUE;
  1909. }
  1910.  
  1911.  
  1912. //----------------------------------------------------------------------------
  1913. //   Description:    Reset the current level pointer to the b-plus tree. This
  1914. //                          function should be called before reading in the root node.
  1915. //    Parameters:    
  1916. //       Returns:    TRUE if successful.
  1917. //----------------------------------------------------------------------------
  1918. static VOID FN_L CfgNodeReset(void)
  1919. {
  1920.     pg->cLevel = 0;
  1921.     return ;
  1922. }
  1923.  
  1924.  
  1925. //----------------------------------------------------------------------------
  1926. //   Description:    Open a configuration file. Create on if necessary.
  1927. //    Parameters:    pcsz     Configuration file name. 
  1928. //                                If null, the base application name is used.
  1929. //       Returns:    TRUE if successful.
  1930. //----------------------------------------------------------------------------
  1931. BOOL FN_E CfgOpen(PCSZ pcsz)
  1932. {
  1933.     CHAR szFile[MAX_PATH];
  1934.     USHORT fs = FL_OPEN|FL_CREATE|FL_READWRITE|FL_DENYNONE|FL_BINARY;
  1935.     FPOS flen;
  1936.  
  1937.     if (pg && pg->fInit)                        // Already open?
  1938.         return TRUE;
  1939.  
  1940.     if (!CfgInitialize())                    // Initialize
  1941.         {
  1942.         CfgClose();
  1943.         return FALSE;
  1944.         }
  1945.     //
  1946.     // Get application name
  1947.     //    Use strcat to append an extension. The DOS application name
  1948.     //    is gauranteed not to have an extension. The UNIX file name may have
  1949.     //    an extension -- but it should be retained (a.out --> a.out.cfg).
  1950.     //    Check if file area is writeable before attempting to create.
  1951.     //
  1952.     if (pcsz && pcsz[0])                        // Use supplied name
  1953.         strcpy(szFile, pcsz);
  1954.     else                                            // Use base application name
  1955.         strcpy(szFile, CommandLineAppName());
  1956.  
  1957.     if (!szFile[0])                            // Default name
  1958.         strcpy(szFile, "config");
  1959.  
  1960.     strcat(szFile, ".cfg");
  1961.     if (!FnameQualify(szFile, NULL, NULL, FNAME_APP_DIR))
  1962.         return FALSE;
  1963.  
  1964.     if (!FnameIsFile(szFile))                // If it doesn't exist, create it in
  1965.         if (FnameIsWriteable(szFile))
  1966.             CfgCreate(szFile);                //  the application directory.
  1967.  
  1968.     if (!FnameIsFile(szFile))                // If was not created, then
  1969.         {                                            // try to put it in the current directory
  1970.         if (!FnameQualify(szFile, NULL, NULL, FNAME_CUR_DIR))
  1971.             return FALSE;
  1972.  
  1973.         if (!FnameIsFile(szFile))            // Wasn't created!
  1974.             if (FnameIsWriteable(szFile))
  1975.                 CfgCreate(szFile);
  1976.         }
  1977.  
  1978.     if (!FnameIsFile(szFile))                // Wasn't created!
  1979.         return FALSE;
  1980.     //
  1981.     //    OK, we have successfully initialized and we have a valid
  1982.     //    file name. Go ahead a finish opening the data file.
  1983.     //
  1984.     if (!FileOpen(&pg->hf, szFile, fs, NULL))
  1985.         return FALSE;
  1986.  
  1987.     flen = FileGetSize(pg->hf);            // Get configuration file size
  1988.     if (flen < HDR_SIZE)                        // Double check that is valid
  1989.         {
  1990.         if (flen >= 0)
  1991.             {
  1992.             Error("Internal configuration file error.\n"
  1993.                 "Configuration file has incorrect size.");
  1994.             }
  1995.         goto ERROR_EXIT;
  1996.         }
  1997.  
  1998.     if (CfgLock())
  1999.         {
  2000. #if COMPILE_DEBUG
  2001.         CfgBlockWalk(FALSE);
  2002. #endif
  2003.         Log(LOG_MEDIUM, "Configuration file opened.\n'%s'", szFile);
  2004.         BaseExitFunc((PFNEXIT)CfgClose, SYS_EXIT_PRIORITY);
  2005.         return CfgUnlock();
  2006.         }
  2007.  
  2008. ERROR_EXIT:
  2009.     CfgClose();
  2010.     return FALSE;
  2011. }
  2012.  
  2013.  
  2014. //----------------------------------------------------------------------------
  2015. //   Description:    Read from a configuration file.
  2016. //    Parameters:    pcszName        Name of the object to read.
  2017. //                        ppb            Variable to receive address of data. 
  2018. //                                        If null, a buffer is    allocated for the data. 
  2019. //                                        The caller is responsible for freeing the data
  2020. //                                        buffer.
  2021. //                        pcb            Variable to receive size of data. 
  2022. //                                        If not null, the initial value of this variable
  2023. //                                        should be the size of the data data buffer or
  2024. //                                        0 for the maximum size.
  2025. //                                        If null, size is not returned.
  2026. //       Returns:    TRUE if successful.
  2027. //----------------------------------------------------------------------------
  2028. BOOL FN_E CfgRead(PCSZ pcszName, PBYTE _FAR_ *ppb, PSIZET pcb)
  2029. {
  2030.     BOOL fResult = FALSE;
  2031.     PCFGKEY pcfgkey;
  2032.  
  2033.     Assert(pcszName && strlen(pcszName) <= KEY_NAME);
  2034.     Assert(ppb);
  2035.  
  2036.     if (!CfgLock())
  2037.        return FALSE;
  2038.  
  2039.     if (!CfgKeyFind((PSZ)pcszName, &pcfgkey))
  2040.         goto ERROR_EXIT;
  2041.  
  2042.     if (pcfgkey == NULL)
  2043.         {
  2044.         Error("Configuration file key not found\n'%s'", pcszName);
  2045.         goto ERROR_EXIT;
  2046.         }
  2047.  
  2048.     if (pcfgkey->fposData)                    // If data is in a separate block,
  2049.         {                                            //  read it in from disk
  2050.         if (!CfgDataRead(pcfgkey->fposData, NULL, ppb, pcb))
  2051.             goto ERROR_EXIT;
  2052.         }
  2053.     else
  2054.         {
  2055.         SIZET cb = (SIZET)pcfgkey->cbData;    // Allocate buffer for data
  2056.  
  2057.         if (pcb && *pcb)
  2058.             cb = MIN(*pcb, cb);
  2059.         if (pcb)
  2060.             *pcb = cb;
  2061.         if (*ppb == NULL)                        // Allocate a buffer
  2062.             {
  2063.             *ppb = (PBYTE)MemAlloc(cb);
  2064.             if (*ppb == NULL)
  2065.                 return FALSE;
  2066.             }
  2067.         memcpy(*ppb, pcfgkey->szName + strlen(pcfgkey->szName) + 1, cb);
  2068.         }
  2069.  
  2070.     fResult = TRUE;
  2071.  
  2072. ERROR_EXIT:
  2073.     if (!CfgUnlock())
  2074.         return FALSE;
  2075.  
  2076.     return fResult;
  2077. }
  2078.  
  2079.  
  2080. //----------------------------------------------------------------------------
  2081. //   Description:    
  2082. //    Parameters:    
  2083. //       Returns:    TRUE if successful.
  2084. //----------------------------------------------------------------------------
  2085. VOID FN_E CfgSetCache(SIZET cBuffers)
  2086. {
  2087.     pg->cCacheMin = MIN(cBuffers, MAX_CACHE);
  2088.     if (pg->fInit)
  2089.         CfgCacheShrink();
  2090.     return ;
  2091. }
  2092.  
  2093.  
  2094. //----------------------------------------------------------------------------
  2095. //   Description:    Unlock a configuration file. 
  2096. //    Parameters:
  2097. //       Returns:    TRUE if successful.
  2098. //----------------------------------------------------------------------------
  2099. static BOOL FN_L CfgUnlock(void)
  2100. {
  2101.     BOOL fResult = TRUE;
  2102.  
  2103.     if (pg->cLock == 0)                        // If not locked, ignore request
  2104.         return FALSE;
  2105.  
  2106.     pg->cLock--;                                // Remove a lock
  2107.     if (pg->cLock)                                // Still locked?
  2108.         return TRUE;                            // Then done
  2109.  
  2110.     if (!CfgCacheFlushAll())                // Flush cache buffers
  2111.         fResult = FALSE;
  2112.  
  2113.     if (!CfgCacheShrink())                    // Shrink cache
  2114.         fResult = FALSE;
  2115.  
  2116.     if (!CfgHeaderWrite(FALSE))            // Write header if dirty
  2117.         fResult = FALSE;
  2118.  
  2119.     if (!FileFlush(pg->hf))                    // Flush data to disk
  2120.         fResult = FALSE;
  2121.  
  2122.     if (!FileUnlock(pg->hf, (FPOS)-1L, 0))        // Unlock the file
  2123.         fResult = FALSE;
  2124.  
  2125. #if OS_OS2 || OS_PM
  2126.     if (DosReleaseMutexSem(pg->hmtx))    // Release semaphore
  2127.         fResult = FALSE;
  2128. #endif
  2129.  
  2130.     pg->tid = 0;
  2131.     return fResult;
  2132. }
  2133.  
  2134.  
  2135. //----------------------------------------------------------------------------
  2136. //   Description:    Write to a configuration file.
  2137. //                          NOTE: Keys are never expanded to hold more data. If the
  2138. //                               data does not fit, it is placed in a separate block.
  2139. //                                Also, keys are never shrunk to recover slack data
  2140. //                                space. The space remains in case it is re-used.
  2141. //    Parameters:    pcszName            Name of the object to write.
  2142. //                        ppb                Pointer to object.
  2143. //                        cb                    Size of object.
  2144. //       Returns:    TRUE if successful.
  2145. //----------------------------------------------------------------------------
  2146. BOOL FN_E CfgWrite(PCSZ pcszName, PBYTE pb, SIZET cb)
  2147. {
  2148.     BOOL fResult = FALSE;
  2149.     SIZET cData;
  2150.     PCFGKEY pcfgkey;
  2151.  
  2152.     Assert(pcszName && strlen(pcszName) <= KEY_NAME);
  2153.     Assert(pb && cb);
  2154.  
  2155.     if (!CfgLock())                            // Lock
  2156.        return FALSE;
  2157.                                                     // Try to find key
  2158.     if (!CfgKeyFind(pcszName, &pcfgkey))
  2159.         goto ERROR_EXIT;
  2160.  
  2161.     if (pcfgkey == NULL)
  2162.         if (!CfgKeyNew(pcszName, &pcfgkey, cb))
  2163.             goto ERROR_EXIT;
  2164.                                           
  2165.     CfgNodeDirty(CfgNodeLevel());            // Cache buffer is dirty!
  2166.  
  2167.  
  2168.     cData = (SIZET)pcfgkey->cb;
  2169.     cData -= KEY_OVERHEAD;
  2170.     cData -= strlen(pcfgkey->szName);
  2171.  
  2172.     pcfgkey->cbData = (LONG)cb;
  2173.     if (cData >= cb)                            // Data fits within key!
  2174.         {
  2175.         if (pcfgkey->fposData)                // Release existing data block
  2176.             if (!CfgBlockMarkEmpty(pcfgkey->fposData))
  2177.                 goto ERROR_EXIT;
  2178.  
  2179.         pcfgkey->fposData = 0;                // Copy data into key
  2180.         memcpy(pcfgkey->szName + strlen(pcfgkey->szName) + 1, pb, cb);
  2181.         }
  2182.     else                                            // Else write data into a separate
  2183.         {                                            // block.
  2184.         pcfgkey->fposData = CfgDataWrite(pcfgkey->fposData, pb, (LONG)cb);
  2185.         if (pcfgkey->fposData < 0)
  2186.             goto ERROR_EXIT;
  2187.         }
  2188.  
  2189.     fResult = TRUE;
  2190. ERROR_EXIT:
  2191.     if (!CfgUnlock())                            // Unlock before leaving!
  2192.         fResult = FALSE;
  2193.  
  2194.     return fResult;
  2195. }
  2196.  
  2197.  
  2198. //----------------------------------------------------------------------------
  2199. //   Description:    Run standard test suite
  2200. //    Parameters:    sTest        Test to run.
  2201. //                                        0        Run all default tests (except).
  2202. //       Returns:    TRUE if successful.
  2203. //----------------------------------------------------------------------------
  2204. #if COMPILE_TEST
  2205. BOOL FN CfgTest(SHORT sTest)
  2206. {
  2207. static CHAR szName[MAX_CFG_NAME+1];
  2208. static CHAR szNamePrev[MAX_CFG_NAME+1];
  2209. static BYTE bData[4096];
  2210. static PSZ apsz[] =
  2211.     {
  2212.     "5a",
  2213.     "5ab",
  2214.     "5abc",
  2215.     "5abcd",
  2216.     "5abcde",
  2217.     "5abcdef",
  2218.     "5abcdefg",
  2219.     "5abcdefgh",
  2220.     "5abcdefghi",
  2221.     "5abcdefghij",
  2222.     "5abcdefghijk",
  2223.     "5abcdefghijkl",
  2224.     "5abcdefghijklm",
  2225.     "5abcdefghijklmn",
  2226.     "5abcdefghijklmno",
  2227.     "5abcdefghijklmnop",
  2228.     "5abcdefghijklmnopq",
  2229.     "5abcdefghijklmnopqr",
  2230.     "5abcdefghijklmnopqrs",
  2231.     "5abcdefghijklmnopqrst",
  2232.     "5abcdefghijklmnopqrstu",
  2233.     "5abcdefghijklmnopqrstuv",
  2234.     "2a",
  2235.     "2ab",
  2236.     "2abc",
  2237.     "6a",
  2238.     "6ab",
  2239.     "6abc",
  2240.     "7ab",
  2241.     "3ab",
  2242.     "7a",
  2243.     "3a",
  2244.     "7abc",
  2245.     "3abc",
  2246.     "4a",
  2247.     "4abc",
  2248.     "4ab",
  2249.     "4abcde",
  2250.     "4abcd",
  2251.     "4abcdefg",
  2252.     "4abcdef",
  2253.     "4abcdefgh",
  2254.     "4abcdefghi",
  2255.     "4abcdefghijk",
  2256.     "4abcdefghijklmno",
  2257.     "4abcdefghijklmnopq",
  2258.     "4abcdefghijklmnopqrst",
  2259.     "4abcdefghijklmnopqrstu",
  2260.     "4abcdefghijklmnopqr",
  2261.     "4abcdefghijklmnopqrs",
  2262.     "4abcdefghij",
  2263.     "4abcdefghijklmnop",
  2264.     "4abcdefghijkl",
  2265.     "4abcdefghijklm",
  2266.     "4abcdefghijklmnopqrstuv",
  2267.     "4abcdefghijklmn",
  2268.     NULL
  2269.     };
  2270.     SIZET i,j;
  2271.     SIZET cb;
  2272.     PBYTE pb;
  2273.     SIZET cElems = 0;
  2274.  
  2275.     if (!CfgOpen(NULL))
  2276.         return FALSE;
  2277.  
  2278.     if (sTest == 1)
  2279.         {
  2280.         Output("Show all configuration file keys.\n");
  2281.         szNamePrev[0] = '\0';
  2282.         if (CfgFindFirst(szName))
  2283.             {
  2284.             do
  2285.                 {
  2286.                 Output("  %-.70s\n", szName);
  2287.                 cElems++;
  2288.                 if (strcmp(szNamePrev, szName) >= 0)
  2289.                     {
  2290.                     Error("Invalid key sort sequence.");
  2291.                     return FALSE;
  2292.                     }
  2293.                 strcpy(szNamePrev, szName);
  2294.                 }
  2295.             while (CfgFindNext(szName));
  2296.             CfgFindClose();
  2297.             }
  2298.         Output("%u elements.\n", cElems);
  2299.         return TRUE;
  2300.         }
  2301.     if (sTest == 2)
  2302.         {
  2303.         LONG l = 0;
  2304.         SIZET i;
  2305.  
  2306.         Output("Write some simple key with no data.\n");
  2307.  
  2308.         for (i = 0; i < 26; ++i)
  2309.             {
  2310.             sprintf(szName, "%c", 'a' + i);
  2311.             if (!CfgWrite(szName, (PBYTE)&l, sizeof(l)))
  2312.                 return FALSE;
  2313.             }
  2314.         }
  2315.     if (sTest == 3)
  2316.         {
  2317.         SIZET i;
  2318.  
  2319.         Output("Write some simple key with lots of data.\n");
  2320.         for (i = 0; i < 26; ++i)
  2321.             {
  2322.             sprintf(szName, "%c", 'A' + i);
  2323.             if (!CfgWrite(szName, (PBYTE)bData, 200))
  2324.                 return FALSE;
  2325.             }
  2326.         }
  2327.     if (sTest == 4)
  2328.         {
  2329.         Output("Configuration file standard test suite.\n");
  2330.  
  2331.         for (i = 0; apsz[i]; ++i)
  2332.             {
  2333.             memset(bData, i, i + 1);
  2334.             if (!CfgWrite(apsz[i], bData, i + 1))
  2335.                 return FALSE;
  2336.             }
  2337.         for (i = 0; apsz[i]; ++i)
  2338.             {
  2339.             cb = sizeof(bData);
  2340.             pb = bData;
  2341.             if (!CfgRead(apsz[i], &pb, &cb))
  2342.                 continue;
  2343.  
  2344.             if (cb != i + 1)
  2345.                 Output("Key '%s' has incorrect size.\n", apsz[i]);
  2346.  
  2347.             for (j = 0; j < i + 1; ++j)
  2348.                 if (bData[j] != i)
  2349.                     {
  2350.                     Output("Key '%s' data is corrupt.\n", apsz[i]);
  2351.                     break;
  2352.                     }
  2353.             }
  2354.         }
  2355.     if (sTest == 5)
  2356.         {
  2357.         Output("Delete all elements from configuration file.\n");
  2358.         while (CfgFindFirst(szName))
  2359.             {
  2360.             CfgFindClose();
  2361.             cElems++;
  2362.             if (!CfgDelete(szName))
  2363.                 return FALSE;
  2364.             }
  2365.         Output("%u elements deleted.\n", cElems);
  2366.         }
  2367.  
  2368.     if (sTest > 5)
  2369.         {
  2370.         Output("Random configuration file stress test.\n");
  2371.  
  2372.         if (CfgFindFirst(szName))            // Count initial elements
  2373.             {
  2374.             do
  2375.                {
  2376.                 cElems++;
  2377.                }
  2378.             while (CfgFindNext(szName));
  2379.             CfgFindClose();
  2380.             }
  2381.         for (i = 5; i < (SIZET)sTest; ++i)
  2382.             {
  2383.             SIZET cOp = Random(0, 7);
  2384.             SIZET cData = 1;
  2385.  
  2386.             Output(".");
  2387.             if (!cElems)                        // If no elements, write some more
  2388.                 cOp = 4;
  2389.             if (cOp <= 2)                        // Existing
  2390.                 {                                    // Pick a name at random
  2391.                 SIZET cWhich = Random(0, cElems - 1);
  2392.                 BOOL fFound = FALSE;
  2393.  
  2394.               if (CfgFindFirst(szName))
  2395.                   {
  2396.                   do
  2397.                       {
  2398.                         if (cWhich == 0)
  2399.                             {
  2400.                             fFound = TRUE;
  2401.                             break;
  2402.                             }
  2403.                         cWhich--;
  2404.                       }
  2405.                   while (CfgFindNext(szName));
  2406.                     CfgFindClose();
  2407.                   }
  2408.                 if (!fFound)
  2409.                     Output("\n%u elements.\n", cElems);
  2410.                 Assert(fFound);
  2411.                 }
  2412.             if (cOp == 3)
  2413.                 strcpy(szName, "$$$$$$$");
  2414.             if (cOp > 3)
  2415.                 {                                    // Create a name at random
  2416.                 SIZET cLen = Random(1, MAX_CFG_NAME);
  2417.  
  2418.                 for (j = 0; j < cLen; ++j)
  2419.                     szName[j] = (CHAR)('A' + Random(0,25));
  2420.                 szName[j] = '\0';
  2421.  
  2422.                 cData = Random(1, sizeof(bData));
  2423.                 if (cData <= 4)                // Don't bother
  2424.                     memset(bData, 0, cData);
  2425.                 else
  2426.                     {
  2427.                     for (j = 4; j < cData; ++j)
  2428.                         bData[j] = (BYTE)Random(0,255);
  2429.                     *((PCRC)bData) = CrcCalc(bData + 4, cData - 4);
  2430.                     }
  2431.                 }
  2432.             switch (cOp)
  2433.                 {
  2434.                 case 0:                            // Read existing
  2435.                     cb = sizeof(bData);
  2436.                     pb = bData;
  2437.                     if (!CfgRead(szName, &pb, &cb))
  2438.                         Output("\nProblem reading existing.");
  2439.                     else if (cb > 4)
  2440.                         {
  2441.                         CRC crc1 = CrcCalc(bData + 4, cb - 4);
  2442.                         CRC crc2 = *((PCRC)bData);
  2443.                         if (crc1 != crc2)
  2444.                             Output("\nData file CRC is corrupt.");
  2445.                         }
  2446.                     break;
  2447.  
  2448.                 case 1:                            // Delete existing
  2449.                     if (!CfgDelete(szName))
  2450.                         Output("\nProblem deleting existing.");
  2451.                     cElems--;
  2452.                     break;
  2453.  
  2454.                 case 2:                            // Find existing
  2455.                     if (!CfgFind(szName))
  2456.                         Output("\nExpected to find name.");
  2457.                     break;
  2458.  
  2459.                 case 3:                            // Find not exist
  2460.                     if (CfgFind(szName))
  2461.                         Output("\nDid not expect to find name.");
  2462.                     break;
  2463.  
  2464.                 default:                            // Write
  2465.                     if (!CfgFind(szName))    // If it doesn't already exist
  2466.                         cElems++;                //  increment element count
  2467.  
  2468.                     if (!CfgWrite(szName, bData, cData))
  2469.                         Output("\nProblem writing data element (%d) '%s'.", cOp, szName);
  2470.                     break;
  2471.                 }
  2472.             }
  2473.         Output("\n");
  2474.         Output("%u elements.\n", cElems);
  2475.         if (CfgFindFirst(szName))            // Count final elements
  2476.             {
  2477.             do
  2478.                 {
  2479.                 cElems--;
  2480.                 }
  2481.             while (CfgFindNext(szName));
  2482.             CfgFindClose();
  2483.             }
  2484.         if (cElems)
  2485.             {
  2486.             Output("Incorrect number of keys remain in file.\n");
  2487.             return FALSE;
  2488.             }
  2489.         }
  2490.     // Default test!
  2491.     Output("Configuration file walk blocks.\n");
  2492.     CfgBlockWalk(TRUE);
  2493.     CfgClose();
  2494.     return TRUE;
  2495. }
  2496. #endif
  2497. //----------------------------------------------------------------------------
  2498. //------------------------------- End of File --------------------------------
  2499. //----------------------------------------------------------------------------
  2500.